This commit is contained in:
John Guo
2025-01-09 12:06:38 +08:00
parent 789baf5541
commit 0083153268
18 changed files with 2446 additions and 2446 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -7,61 +7,61 @@
package gdb
import (
"context"
"database/sql/driver"
"reflect"
"strings"
"time"
"context"
"database/sql/driver"
"reflect"
"strings"
"time"
"github.com/gogf/gf/v2/encoding/gbinary"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/encoding/gbinary"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
// GetFieldTypeStr retrieves and returns the field type string for certain field by name.
func (c *Core) GetFieldTypeStr(ctx context.Context, fieldName, table, schema string) string {
field := c.GetFieldType(ctx, fieldName, table, schema)
if field != nil {
// Kinds of data type examples:
// year(4)
// datetime
// varchar(64)
// bigint(20)
// int(10) unsigned
typeName := gstr.StrTillEx(field.Type, "(") // int(10) unsigned -> int
if typeName != "" {
typeName = gstr.Trim(typeName)
} else {
typeName = field.Type
}
return typeName
}
return ""
field := c.GetFieldType(ctx, fieldName, table, schema)
if field != nil {
// Kinds of data type examples:
// year(4)
// datetime
// varchar(64)
// bigint(20)
// int(10) unsigned
typeName := gstr.StrTillEx(field.Type, "(") // int(10) unsigned -> int
if typeName != "" {
typeName = gstr.Trim(typeName)
} else {
typeName = field.Type
}
return typeName
}
return ""
}
// GetFieldType retrieves and returns the field type object for certain field by name.
func (c *Core) GetFieldType(ctx context.Context, fieldName, table, schema string) *TableField {
fieldsMap, err := c.db.TableFields(ctx, table, schema)
if err != nil {
intlog.Errorf(
ctx,
`TableFields failed for table "%s", schema "%s": %+v`,
table, schema, err,
)
return nil
}
for tableFieldName, tableField := range fieldsMap {
if tableFieldName == fieldName {
return tableField
}
}
return nil
fieldsMap, err := c.db.TableFields(ctx, table, schema)
if err != nil {
intlog.Errorf(
ctx,
`TableFields failed for table "%s", schema "%s": %+v`,
table, schema, err,
)
return nil
}
for tableFieldName, tableField := range fieldsMap {
if tableFieldName == fieldName {
return tableField
}
}
return nil
}
// ConvertDataForRecord is a very important function, which does converting for any data that
@ -70,420 +70,420 @@ func (c *Core) GetFieldType(ctx context.Context, fieldName, table, schema string
// The parameter `value` should be type of *map/map/*struct/struct.
// It supports embedded struct definition for struct.
func (c *Core) ConvertDataForRecord(ctx context.Context, value interface{}, table string) (map[string]interface{}, error) {
var (
err error
data = MapOrStructToMapDeep(value, true)
)
for fieldName, fieldValue := range data {
var fieldType = c.GetFieldTypeStr(ctx, fieldName, table, c.GetSchema())
data[fieldName], err = c.db.ConvertValueForField(
ctx,
fieldType,
fieldValue,
)
if err != nil {
return nil, gerror.Wrapf(err, `ConvertDataForRecord failed for value: %#v`, fieldValue)
}
}
return data, nil
var (
err error
data = MapOrStructToMapDeep(value, true)
)
for fieldName, fieldValue := range data {
var fieldType = c.GetFieldTypeStr(ctx, fieldName, table, c.GetSchema())
data[fieldName], err = c.db.ConvertValueForField(
ctx,
fieldType,
fieldValue,
)
if err != nil {
return nil, gerror.Wrapf(err, `ConvertDataForRecord failed for value: %#v`, fieldValue)
}
}
return data, nil
}
// ConvertValueForField converts value to the type of the record field.
// The parameter `fieldType` is the target record field.
// The parameter `fieldValue` is the value that to be committed to record field.
func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
var (
err error
convertedValue = fieldValue
)
switch fieldValue.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
goto Default
}
// If `value` implements interface `driver.Valuer`, it then uses the interface for value converting.
if valuer, ok := fieldValue.(driver.Valuer); ok {
if convertedValue, err = valuer.Value(); err != nil {
return nil, err
}
return convertedValue, nil
}
var (
err error
convertedValue = fieldValue
)
switch fieldValue.(type) {
case time.Time, *time.Time, gtime.Time, *gtime.Time:
goto Default
}
// If `value` implements interface `driver.Valuer`, it then uses the interface for value converting.
if valuer, ok := fieldValue.(driver.Valuer); ok {
if convertedValue, err = valuer.Value(); err != nil {
return nil, err
}
return convertedValue, nil
}
Default:
// Default value converting.
var (
rvValue = reflect.ValueOf(fieldValue)
rvKind = rvValue.Kind()
)
for rvKind == reflect.Ptr {
rvValue = rvValue.Elem()
rvKind = rvValue.Kind()
}
switch rvKind {
case reflect.Invalid:
convertedValue = nil
// Default value converting.
var (
rvValue = reflect.ValueOf(fieldValue)
rvKind = rvValue.Kind()
)
for rvKind == reflect.Ptr {
rvValue = rvValue.Elem()
rvKind = rvValue.Kind()
}
switch rvKind {
case reflect.Invalid:
convertedValue = nil
case reflect.Slice, reflect.Array, reflect.Map:
// It should ignore the bytes type.
if _, ok := fieldValue.([]byte); !ok {
// Convert the value to JSON.
convertedValue, err = json.Marshal(fieldValue)
if err != nil {
return nil, err
}
}
case reflect.Struct:
switch r := fieldValue.(type) {
// If the time is zero, it then updates it to nil,
// which will insert/update the value to database as "null".
case time.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Format("2006")
case fieldTypeDate:
convertedValue = r.Format("2006-01-02")
case fieldTypeTime:
convertedValue = r.Format("15:04:05")
default:
}
}
case reflect.Slice, reflect.Array, reflect.Map:
// It should ignore the bytes type.
if _, ok := fieldValue.([]byte); !ok {
// Convert the value to JSON.
convertedValue, err = json.Marshal(fieldValue)
if err != nil {
return nil, err
}
}
case reflect.Struct:
switch r := fieldValue.(type) {
// If the time is zero, it then updates it to nil,
// which will insert/update the value to database as "null".
case time.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Format("2006")
case fieldTypeDate:
convertedValue = r.Format("2006-01-02")
case fieldTypeTime:
convertedValue = r.Format("15:04:05")
default:
}
}
case *time.Time:
if r == nil {
// Nothing to do.
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Format("2006")
case fieldTypeDate:
convertedValue = r.Format("2006-01-02")
case fieldTypeTime:
convertedValue = r.Format("15:04:05")
default:
}
}
case *time.Time:
if r == nil {
// Nothing to do.
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Format("2006")
case fieldTypeDate:
convertedValue = r.Format("2006-01-02")
case fieldTypeTime:
convertedValue = r.Format("15:04:05")
default:
}
}
case gtime.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Layout("2006")
case fieldTypeDate:
convertedValue = r.Layout("2006-01-02")
case fieldTypeTime:
convertedValue = r.Layout("15:04:05")
default:
convertedValue = r.Time
}
}
case gtime.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Layout("2006")
case fieldTypeDate:
convertedValue = r.Layout("2006-01-02")
case fieldTypeTime:
convertedValue = r.Layout("15:04:05")
default:
convertedValue = r.Time
}
}
case *gtime.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Layout("2006")
case fieldTypeDate:
convertedValue = r.Layout("2006-01-02")
case fieldTypeTime:
convertedValue = r.Layout("15:04:05")
default:
convertedValue = r.Time
}
}
case *gtime.Time:
if r.IsZero() {
convertedValue = nil
} else {
switch fieldType {
case fieldTypeYear:
convertedValue = r.Layout("2006")
case fieldTypeDate:
convertedValue = r.Layout("2006-01-02")
case fieldTypeTime:
convertedValue = r.Layout("15:04:05")
default:
convertedValue = r.Time
}
}
case Counter, *Counter:
// Nothing to do.
case Counter, *Counter:
// Nothing to do.
default:
// If `value` implements interface iNil,
// check its IsNil() function, if got ture,
// which will insert/update the value to database as "null".
if v, ok := fieldValue.(iNil); ok && v.IsNil() {
convertedValue = nil
} else if s, ok := fieldValue.(iString); ok {
// Use string conversion in default.
convertedValue = s.String()
} else {
// Convert the value to JSON.
convertedValue, err = json.Marshal(fieldValue)
if err != nil {
return nil, err
}
}
}
default:
}
default:
// If `value` implements interface iNil,
// check its IsNil() function, if got ture,
// which will insert/update the value to database as "null".
if v, ok := fieldValue.(iNil); ok && v.IsNil() {
convertedValue = nil
} else if s, ok := fieldValue.(iString); ok {
// Use string conversion in default.
convertedValue = s.String()
} else {
// Convert the value to JSON.
convertedValue, err = json.Marshal(fieldValue)
if err != nil {
return nil, err
}
}
}
default:
}
return convertedValue, nil
return convertedValue, nil
}
// CheckLocalTypeForField checks and returns corresponding type for given db type.
// The `fieldType` is retrieved from ColumnTypes of db driver, example:
// UNSIGNED INT
func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, _ interface{}) (LocalType, error) {
var (
typeName string
typePattern string
)
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
if len(match) == 3 {
typeName = gstr.Trim(match[1])
typePattern = gstr.Trim(match[2])
} else {
var array = gstr.SplitAndTrim(fieldType, " ")
if gstr.Equal(array[0], "unsigned") {
typeName = array[1]
} else {
typeName = array[0]
}
}
var (
typeName string
typePattern string
)
match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
if len(match) == 3 {
typeName = gstr.Trim(match[1])
typePattern = gstr.Trim(match[2])
} else {
var array = gstr.SplitAndTrim(fieldType, " ")
if gstr.Equal(array[0], "unsigned") {
typeName = array[1]
} else {
typeName = array[0]
}
}
typeName = strings.ToLower(typeName)
typeName = strings.ToLower(typeName)
switch typeName {
case
fieldTypeBinary,
fieldTypeVarbinary,
fieldTypeBlob,
fieldTypeTinyblob,
fieldTypeMediumblob,
fieldTypeLongblob:
return LocalTypeBytes, nil
switch typeName {
case
fieldTypeBinary,
fieldTypeVarbinary,
fieldTypeBlob,
fieldTypeTinyblob,
fieldTypeMediumblob,
fieldTypeLongblob:
return LocalTypeBytes, nil
case
fieldTypeInt,
fieldTypeTinyint,
fieldTypeSmallInt,
fieldTypeSmallint,
fieldTypeMediumInt,
fieldTypeMediumint,
fieldTypeSerial:
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint, nil
}
return LocalTypeInt, nil
case
fieldTypeInt,
fieldTypeTinyint,
fieldTypeSmallInt,
fieldTypeSmallint,
fieldTypeMediumInt,
fieldTypeMediumint,
fieldTypeSerial:
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint, nil
}
return LocalTypeInt, nil
case
fieldTypeBigInt,
fieldTypeBigint,
fieldTypeBigserial:
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint64, nil
}
return LocalTypeInt64, nil
case
fieldTypeBigInt,
fieldTypeBigint,
fieldTypeBigserial:
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint64, nil
}
return LocalTypeInt64, nil
case
fieldTypeReal:
return LocalTypeFloat32, nil
case
fieldTypeReal:
return LocalTypeFloat32, nil
case
fieldTypeDecimal,
fieldTypeMoney,
fieldTypeNumeric,
fieldTypeSmallmoney:
return LocalTypeString, nil
case
fieldTypeFloat,
fieldTypeDouble:
return LocalTypeFloat64, nil
case
fieldTypeDecimal,
fieldTypeMoney,
fieldTypeNumeric,
fieldTypeSmallmoney:
return LocalTypeString, nil
case
fieldTypeFloat,
fieldTypeDouble:
return LocalTypeFloat64, nil
case
fieldTypeBit:
// It is suggested using bit(1) as boolean.
if typePattern == "1" {
return LocalTypeBool, nil
}
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint64Bytes, nil
}
return LocalTypeInt64Bytes, nil
case
fieldTypeBit:
// It is suggested using bit(1) as boolean.
if typePattern == "1" {
return LocalTypeBool, nil
}
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint64Bytes, nil
}
return LocalTypeInt64Bytes, nil
case
fieldTypeBool:
return LocalTypeBool, nil
case
fieldTypeBool:
return LocalTypeBool, nil
case
fieldTypeDate:
return LocalTypeDate, nil
case
fieldTypeDate:
return LocalTypeDate, nil
case
fieldTypeTime:
return LocalTypeTime, nil
case
fieldTypeTime:
return LocalTypeTime, nil
case
fieldTypeDatetime,
fieldTypeTimestamp,
fieldTypeTimestampz:
return LocalTypeDatetime, nil
case
fieldTypeDatetime,
fieldTypeTimestamp,
fieldTypeTimestampz:
return LocalTypeDatetime, nil
case
fieldTypeJson:
return LocalTypeJson, nil
case
fieldTypeJson:
return LocalTypeJson, nil
case
fieldTypeJsonb:
return LocalTypeJsonb, nil
case
fieldTypeJsonb:
return LocalTypeJsonb, nil
default:
// Auto-detect field type, using key match.
switch {
case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
return LocalTypeString, nil
default:
// Auto-detect field type, using key match.
switch {
case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
return LocalTypeString, nil
case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
return LocalTypeFloat64, nil
case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
return LocalTypeFloat64, nil
case strings.Contains(typeName, "bool"):
return LocalTypeBool, nil
case strings.Contains(typeName, "bool"):
return LocalTypeBool, nil
case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
return LocalTypeBytes, nil
case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
return LocalTypeBytes, nil
case strings.Contains(typeName, "int"):
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint, nil
}
return LocalTypeInt, nil
case strings.Contains(typeName, "int"):
if gstr.ContainsI(fieldType, "unsigned") {
return LocalTypeUint, nil
}
return LocalTypeInt, nil
case strings.Contains(typeName, "time"):
return LocalTypeDatetime, nil
case strings.Contains(typeName, "time"):
return LocalTypeDatetime, nil
case strings.Contains(typeName, "date"):
return LocalTypeDatetime, nil
case strings.Contains(typeName, "date"):
return LocalTypeDatetime, nil
default:
return LocalTypeString, nil
}
}
default:
return LocalTypeString, nil
}
}
}
// ConvertValueForLocal converts value to local Golang type of value according field type name from database.
// The parameter `fieldType` is in lower case, like:
// `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
func (c *Core) ConvertValueForLocal(
ctx context.Context, fieldType string, fieldValue interface{},
ctx context.Context, fieldType string, fieldValue interface{},
) (interface{}, error) {
// If there's no type retrieved, it returns the `fieldValue` directly
// to use its original data type, as `fieldValue` is type of interface{}.
if fieldType == "" {
return fieldValue, nil
}
typeName, err := c.db.CheckLocalTypeForField(ctx, fieldType, fieldValue)
if err != nil {
return nil, err
}
switch typeName {
case LocalTypeBytes:
var typeNameStr = string(typeName)
if strings.Contains(typeNameStr, "binary") || strings.Contains(typeNameStr, "blob") {
return fieldValue, nil
}
return gconv.Bytes(fieldValue), nil
// If there's no type retrieved, it returns the `fieldValue` directly
// to use its original data type, as `fieldValue` is type of interface{}.
if fieldType == "" {
return fieldValue, nil
}
typeName, err := c.db.CheckLocalTypeForField(ctx, fieldType, fieldValue)
if err != nil {
return nil, err
}
switch typeName {
case LocalTypeBytes:
var typeNameStr = string(typeName)
if strings.Contains(typeNameStr, "binary") || strings.Contains(typeNameStr, "blob") {
return fieldValue, nil
}
return gconv.Bytes(fieldValue), nil
case LocalTypeInt:
return gconv.Int(gconv.String(fieldValue)), nil
case LocalTypeInt:
return gconv.Int(gconv.String(fieldValue)), nil
case LocalTypeUint:
return gconv.Uint(gconv.String(fieldValue)), nil
case LocalTypeUint:
return gconv.Uint(gconv.String(fieldValue)), nil
case LocalTypeInt64:
return gconv.Int64(gconv.String(fieldValue)), nil
case LocalTypeInt64:
return gconv.Int64(gconv.String(fieldValue)), nil
case LocalTypeUint64:
return gconv.Uint64(gconv.String(fieldValue)), nil
case LocalTypeUint64:
return gconv.Uint64(gconv.String(fieldValue)), nil
case LocalTypeInt64Bytes:
return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue)), nil
case LocalTypeInt64Bytes:
return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue)), nil
case LocalTypeUint64Bytes:
return gbinary.BeDecodeToUint64(gconv.Bytes(fieldValue)), nil
case LocalTypeUint64Bytes:
return gbinary.BeDecodeToUint64(gconv.Bytes(fieldValue)), nil
case LocalTypeFloat32:
return gconv.Float32(gconv.String(fieldValue)), nil
case LocalTypeFloat32:
return gconv.Float32(gconv.String(fieldValue)), nil
case LocalTypeFloat64:
return gconv.Float64(gconv.String(fieldValue)), nil
case LocalTypeFloat64:
return gconv.Float64(gconv.String(fieldValue)), nil
case LocalTypeBool:
s := gconv.String(fieldValue)
// mssql is true|false string.
if strings.EqualFold(s, "true") {
return 1, nil
}
if strings.EqualFold(s, "false") {
return 0, nil
}
return gconv.Bool(fieldValue), nil
case LocalTypeBool:
s := gconv.String(fieldValue)
// mssql is true|false string.
if strings.EqualFold(s, "true") {
return 1, nil
}
if strings.EqualFold(s, "false") {
return 0, nil
}
return gconv.Bool(fieldValue), nil
case LocalTypeDate:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t).Format("Y-m-d"), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t.Format("Y-m-d"), nil
case LocalTypeDate:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t).Format("Y-m-d"), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t.Format("Y-m-d"), nil
case LocalTypeTime:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t).Format("H:i:s"), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t.Format("H:i:s"), nil
case LocalTypeTime:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t).Format("H:i:s"), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t.Format("H:i:s"), nil
case LocalTypeDatetime:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t, nil
case LocalTypeDatetime:
if t, ok := fieldValue.(time.Time); ok {
return gtime.NewFromTime(t), nil
}
t, _ := gtime.StrToTime(gconv.String(fieldValue))
return t, nil
default:
return gconv.String(fieldValue), nil
}
default:
return gconv.String(fieldValue), nil
}
}
// mappingAndFilterData automatically mappings the map key to table field and removes
// all key-value pairs that are not the field of given table.
func (c *Core) mappingAndFilterData(ctx context.Context, schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
fieldsMap, err := c.db.TableFields(ctx, c.guessPrimaryTableName(table), schema)
if err != nil {
return nil, err
}
if len(fieldsMap) == 0 {
return nil, gerror.Newf(`The table %s may not exist, or the table contains no fields`, table)
}
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k := range fieldsMap {
fieldsKeyMap[k] = nil
}
// Automatic data key to table field name mapping.
var foundKey string
for dataKey, dataValue := range data {
if _, ok := fieldsKeyMap[dataKey]; !ok {
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
if foundKey != "" {
if _, ok = data[foundKey]; !ok {
data[foundKey] = dataValue
}
delete(data, dataKey)
}
}
}
// Data filtering.
// It deletes all key-value pairs that has incorrect field name.
if filter {
for dataKey := range data {
if _, ok := fieldsMap[dataKey]; !ok {
delete(data, dataKey)
}
}
if len(data) == 0 {
return nil, gerror.Newf(`input data match no fields in table %s`, table)
}
}
return data, nil
fieldsMap, err := c.db.TableFields(ctx, c.guessPrimaryTableName(table), schema)
if err != nil {
return nil, err
}
if len(fieldsMap) == 0 {
return nil, gerror.Newf(`The table %s may not exist, or the table contains no fields`, table)
}
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k := range fieldsMap {
fieldsKeyMap[k] = nil
}
// Automatic data key to table field name mapping.
var foundKey string
for dataKey, dataValue := range data {
if _, ok := fieldsKeyMap[dataKey]; !ok {
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
if foundKey != "" {
if _, ok = data[foundKey]; !ok {
data[foundKey] = dataValue
}
delete(data, dataKey)
}
}
}
// Data filtering.
// It deletes all key-value pairs that has incorrect field name.
if filter {
for dataKey := range data {
if _, ok := fieldsMap[dataKey]; !ok {
delete(data, dataKey)
}
}
if len(data) == 0 {
return nil, gerror.Newf(`input data match no fields in table %s`, table)
}
}
return data, nil
}

View File

@ -8,298 +8,298 @@
package gdb
import (
"context"
"database/sql"
"fmt"
"reflect"
"context"
"database/sql"
"fmt"
"reflect"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/guid"
)
// Query commits one query SQL to underlying driver and returns the execution result.
// It is most commonly used for data querying.
func (c *Core) Query(ctx context.Context, sql string, args ...interface{}) (result Result, err error) {
return c.db.DoQuery(ctx, nil, sql, args...)
return c.db.DoQuery(ctx, nil, sql, args...)
}
// DoQuery commits the sql string and its arguments to underlying driver
// through given link object and returns the execution result.
func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (result Result, err error) {
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.GetSqlTX()}
} else if link, err = c.SlaveLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.GetSqlTX()}
}
}
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.GetSqlTX()}
} else if link, err = c.SlaveLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.GetSqlTX()}
}
}
// Sql filtering.
sql, args = c.FormatSqlBeforeExecuting(sql, args)
sql, args, err = c.db.DoFilter(ctx, link, sql, args)
if err != nil {
return nil, err
}
// SQL format and retrieve.
if v := ctx.Value(ctxKeyCatchSQL); v != nil {
var (
manager = v.(*CatchSQLManager)
formattedSql = FormatSqlWithArgs(sql, args)
)
manager.SQLArray.Append(formattedSql)
if !manager.DoCommit && ctx.Value(ctxKeyInternalProducedSQL) == nil {
return nil, nil
}
}
// Link execution.
var out DoCommitOutput
out, err = c.db.DoCommit(ctx, DoCommitInput{
Link: link,
Sql: sql,
Args: args,
Stmt: nil,
Type: SqlTypeQueryContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return nil, err
}
return out.Records, err
// Sql filtering.
sql, args = c.FormatSqlBeforeExecuting(sql, args)
sql, args, err = c.db.DoFilter(ctx, link, sql, args)
if err != nil {
return nil, err
}
// SQL format and retrieve.
if v := ctx.Value(ctxKeyCatchSQL); v != nil {
var (
manager = v.(*CatchSQLManager)
formattedSql = FormatSqlWithArgs(sql, args)
)
manager.SQLArray.Append(formattedSql)
if !manager.DoCommit && ctx.Value(ctxKeyInternalProducedSQL) == nil {
return nil, nil
}
}
// Link execution.
var out DoCommitOutput
out, err = c.db.DoCommit(ctx, DoCommitInput{
Link: link,
Sql: sql,
Args: args,
Stmt: nil,
Type: SqlTypeQueryContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return nil, err
}
return out.Records, err
}
// Exec commits one query SQL to underlying driver and returns the execution result.
// It is most commonly used for data inserting and updating.
func (c *Core) Exec(ctx context.Context, sql string, args ...interface{}) (result sql.Result, err error) {
return c.db.DoExec(ctx, nil, sql, args...)
return c.db.DoExec(ctx, nil, sql, args...)
}
// DoExec commits the sql string and its arguments to underlying driver
// through given link object and returns the execution result.
func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) {
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.GetSqlTX()}
} else if link, err = c.MasterLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.GetSqlTX()}
}
}
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.GetSqlTX()}
} else if link, err = c.MasterLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.GetSqlTX()}
}
}
// SQL filtering.
sql, args = c.FormatSqlBeforeExecuting(sql, args)
sql, args, err = c.db.DoFilter(ctx, link, sql, args)
if err != nil {
return nil, err
}
// SQL format and retrieve.
if v := ctx.Value(ctxKeyCatchSQL); v != nil {
var (
manager = v.(*CatchSQLManager)
formattedSql = FormatSqlWithArgs(sql, args)
)
manager.SQLArray.Append(formattedSql)
if !manager.DoCommit && ctx.Value(ctxKeyInternalProducedSQL) == nil {
return new(SqlResult), nil
}
}
// Link execution.
var out DoCommitOutput
out, err = c.db.DoCommit(ctx, DoCommitInput{
Link: link,
Sql: sql,
Args: args,
Stmt: nil,
Type: SqlTypeExecContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return nil, err
}
return out.Result, err
// SQL filtering.
sql, args = c.FormatSqlBeforeExecuting(sql, args)
sql, args, err = c.db.DoFilter(ctx, link, sql, args)
if err != nil {
return nil, err
}
// SQL format and retrieve.
if v := ctx.Value(ctxKeyCatchSQL); v != nil {
var (
manager = v.(*CatchSQLManager)
formattedSql = FormatSqlWithArgs(sql, args)
)
manager.SQLArray.Append(formattedSql)
if !manager.DoCommit && ctx.Value(ctxKeyInternalProducedSQL) == nil {
return new(SqlResult), nil
}
}
// Link execution.
var out DoCommitOutput
out, err = c.db.DoCommit(ctx, DoCommitInput{
Link: link,
Sql: sql,
Args: args,
Stmt: nil,
Type: SqlTypeExecContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return nil, err
}
return out.Result, err
}
// DoFilter is a hook function, which filters the sql and its arguments before it's committed to underlying driver.
// The parameter `link` specifies the current database connection operation object. You can modify the sql
// string `sql` and its arguments `args` as you wish before they're committed to driver.
func (c *Core) DoFilter(
ctx context.Context, link Link, sql string, args []interface{},
ctx context.Context, link Link, sql string, args []interface{},
) (newSql string, newArgs []interface{}, err error) {
return sql, args, nil
return sql, args, nil
}
// DoCommit commits current sql and arguments to underlying sql driver.
func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) {
var (
sqlTx *sql.Tx
sqlStmt *sql.Stmt
sqlRows *sql.Rows
sqlResult sql.Result
stmtSqlRows *sql.Rows
stmtSqlRow *sql.Row
rowsAffected int64
cancelFuncForTimeout context.CancelFunc
formattedSql = FormatSqlWithArgs(in.Sql, in.Args)
timestampMilli1 = gtime.TimestampMilli()
)
var (
sqlTx *sql.Tx
sqlStmt *sql.Stmt
sqlRows *sql.Rows
sqlResult sql.Result
stmtSqlRows *sql.Rows
stmtSqlRow *sql.Row
rowsAffected int64
cancelFuncForTimeout context.CancelFunc
formattedSql = FormatSqlWithArgs(in.Sql, in.Args)
timestampMilli1 = gtime.TimestampMilli()
)
// Trace span start.
tr := otel.GetTracerProvider().Tracer(traceInstrumentName, trace.WithInstrumentationVersion(gf.VERSION))
ctx, span := tr.Start(ctx, string(in.Type), trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()
// Trace span start.
tr := otel.GetTracerProvider().Tracer(traceInstrumentName, trace.WithInstrumentationVersion(gf.VERSION))
ctx, span := tr.Start(ctx, string(in.Type), trace.WithSpanKind(trace.SpanKindInternal))
defer span.End()
// Execution by type.
switch in.Type {
case SqlTypeBegin:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeTrans)
formattedSql = fmt.Sprintf(
`%s (IosolationLevel: %s, ReadOnly: %t)`,
formattedSql, in.TxOptions.Isolation.String(), in.TxOptions.ReadOnly,
)
if sqlTx, err = in.Db.BeginTx(ctx, &in.TxOptions); err == nil {
out.Tx = &TXCore{
db: c.db,
tx: sqlTx,
ctx: context.WithValue(ctx, transactionIdForLoggerCtx, transactionIdGenerator.Add(1)),
master: in.Db,
transactionId: guid.S(),
cancelFunc: cancelFuncForTimeout,
}
ctx = out.Tx.GetCtx()
}
out.RawResult = sqlTx
// Execution by type.
switch in.Type {
case SqlTypeBegin:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeTrans)
formattedSql = fmt.Sprintf(
`%s (IosolationLevel: %s, ReadOnly: %t)`,
formattedSql, in.TxOptions.Isolation.String(), in.TxOptions.ReadOnly,
)
if sqlTx, err = in.Db.BeginTx(ctx, &in.TxOptions); err == nil {
out.Tx = &TXCore{
db: c.db,
tx: sqlTx,
ctx: context.WithValue(ctx, transactionIdForLoggerCtx, transactionIdGenerator.Add(1)),
master: in.Db,
transactionId: guid.S(),
cancelFunc: cancelFuncForTimeout,
}
ctx = out.Tx.GetCtx()
}
out.RawResult = sqlTx
case SqlTypeTXCommit:
if in.TxCancelFunc != nil {
defer in.TxCancelFunc()
}
err = in.Tx.Commit()
case SqlTypeTXCommit:
if in.TxCancelFunc != nil {
defer in.TxCancelFunc()
}
err = in.Tx.Commit()
case SqlTypeTXRollback:
if in.TxCancelFunc != nil {
defer in.TxCancelFunc()
}
err = in.Tx.Rollback()
case SqlTypeTXRollback:
if in.TxCancelFunc != nil {
defer in.TxCancelFunc()
}
err = in.Tx.Rollback()
case SqlTypeExecContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
defer cancelFuncForTimeout()
if c.db.GetDryRun() {
sqlResult = new(SqlResult)
} else {
sqlResult, err = in.Link.ExecContext(ctx, in.Sql, in.Args...)
}
out.RawResult = sqlResult
case SqlTypeExecContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
defer cancelFuncForTimeout()
if c.db.GetDryRun() {
sqlResult = new(SqlResult)
} else {
sqlResult, err = in.Link.ExecContext(ctx, in.Sql, in.Args...)
}
out.RawResult = sqlResult
case SqlTypeQueryContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
sqlRows, err = in.Link.QueryContext(ctx, in.Sql, in.Args...)
out.RawResult = sqlRows
case SqlTypeQueryContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
sqlRows, err = in.Link.QueryContext(ctx, in.Sql, in.Args...)
out.RawResult = sqlRows
case SqlTypePrepareContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypePrepare)
defer cancelFuncForTimeout()
sqlStmt, err = in.Link.PrepareContext(ctx, in.Sql)
out.RawResult = sqlStmt
case SqlTypePrepareContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypePrepare)
defer cancelFuncForTimeout()
sqlStmt, err = in.Link.PrepareContext(ctx, in.Sql)
out.RawResult = sqlStmt
case SqlTypeStmtExecContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
defer cancelFuncForTimeout()
if c.db.GetDryRun() {
sqlResult = new(SqlResult)
} else {
sqlResult, err = in.Stmt.ExecContext(ctx, in.Args...)
}
out.RawResult = sqlResult
case SqlTypeStmtExecContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeExec)
defer cancelFuncForTimeout()
if c.db.GetDryRun() {
sqlResult = new(SqlResult)
} else {
sqlResult, err = in.Stmt.ExecContext(ctx, in.Args...)
}
out.RawResult = sqlResult
case SqlTypeStmtQueryContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
stmtSqlRows, err = in.Stmt.QueryContext(ctx, in.Args...)
out.RawResult = stmtSqlRows
case SqlTypeStmtQueryContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
stmtSqlRows, err = in.Stmt.QueryContext(ctx, in.Args...)
out.RawResult = stmtSqlRows
case SqlTypeStmtQueryRowContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
stmtSqlRow = in.Stmt.QueryRowContext(ctx, in.Args...)
out.RawResult = stmtSqlRow
case SqlTypeStmtQueryRowContext:
ctx, cancelFuncForTimeout = c.GetCtxTimeout(ctx, ctxTimeoutTypeQuery)
defer cancelFuncForTimeout()
stmtSqlRow = in.Stmt.QueryRowContext(ctx, in.Args...)
out.RawResult = stmtSqlRow
default:
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid SqlType "%s"`, in.Type))
}
// Result handling.
switch {
case sqlResult != nil && !c.GetIgnoreResultFromCtx(ctx):
rowsAffected, err = sqlResult.RowsAffected()
out.Result = sqlResult
default:
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid SqlType "%s"`, in.Type))
}
// Result handling.
switch {
case sqlResult != nil && !c.GetIgnoreResultFromCtx(ctx):
rowsAffected, err = sqlResult.RowsAffected()
out.Result = sqlResult
case sqlRows != nil:
out.Records, err = c.RowsToResult(ctx, sqlRows)
rowsAffected = int64(len(out.Records))
case sqlRows != nil:
out.Records, err = c.RowsToResult(ctx, sqlRows)
rowsAffected = int64(len(out.Records))
case sqlStmt != nil:
out.Stmt = &Stmt{
Stmt: sqlStmt,
core: c,
link: in.Link,
sql: in.Sql,
}
}
var (
timestampMilli2 = gtime.TimestampMilli()
sqlObj = &Sql{
Sql: in.Sql,
Type: in.Type,
Args: in.Args,
Format: formattedSql,
Error: err,
Start: timestampMilli1,
End: timestampMilli2,
Group: c.db.GetGroup(),
Schema: c.db.GetSchema(),
RowsAffected: rowsAffected,
IsTransaction: in.IsTransaction,
}
)
case sqlStmt != nil:
out.Stmt = &Stmt{
Stmt: sqlStmt,
core: c,
link: in.Link,
sql: in.Sql,
}
}
var (
timestampMilli2 = gtime.TimestampMilli()
sqlObj = &Sql{
Sql: in.Sql,
Type: in.Type,
Args: in.Args,
Format: formattedSql,
Error: err,
Start: timestampMilli1,
End: timestampMilli2,
Group: c.db.GetGroup(),
Schema: c.db.GetSchema(),
RowsAffected: rowsAffected,
IsTransaction: in.IsTransaction,
}
)
// Tracing.
c.traceSpanEnd(ctx, span, sqlObj)
// Tracing.
c.traceSpanEnd(ctx, span, sqlObj)
// Logging.
if c.db.GetDebug() {
c.writeSqlToLogger(ctx, sqlObj)
}
if err != nil && err != sql.ErrNoRows {
err = gerror.WrapCode(
gcode.CodeDbOperationError,
err,
FormatSqlWithArgs(in.Sql, in.Args),
)
}
return out, err
// Logging.
if c.db.GetDebug() {
c.writeSqlToLogger(ctx, sqlObj)
}
if err != nil && err != sql.ErrNoRows {
err = gerror.WrapCode(
gcode.CodeDbOperationError,
err,
FormatSqlWithArgs(in.Sql, in.Args),
)
}
return out, err
}
// Prepare creates a prepared statement for later queries or executions.
@ -311,200 +311,200 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp
// The parameter `execOnMaster` specifies whether executing the sql on master node,
// or else it executes the sql on slave node if master-slave configured.
func (c *Core) Prepare(ctx context.Context, sql string, execOnMaster ...bool) (*Stmt, error) {
var (
err error
link Link
)
if len(execOnMaster) > 0 && execOnMaster[0] {
if link, err = c.MasterLink(); err != nil {
return nil, err
}
} else {
if link, err = c.SlaveLink(); err != nil {
return nil, err
}
}
return c.db.DoPrepare(ctx, link, sql)
var (
err error
link Link
)
if len(execOnMaster) > 0 && execOnMaster[0] {
if link, err = c.MasterLink(); err != nil {
return nil, err
}
} else {
if link, err = c.SlaveLink(); err != nil {
return nil, err
}
}
return c.db.DoPrepare(ctx, link, sql)
}
// DoPrepare calls prepare function on given link object and returns the statement object.
func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (stmt *Stmt, err error) {
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.GetSqlTX()}
} else {
// Or else it creates one from master node.
if link, err = c.MasterLink(); err != nil {
return nil, err
}
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.GetSqlTX()}
}
}
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.GetSqlTX()}
} else {
// Or else it creates one from master node.
if link, err = c.MasterLink(); err != nil {
return nil, err
}
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.GetSqlTX()}
}
}
if c.db.GetConfig().PrepareTimeout > 0 {
// DO NOT USE cancel function in prepare statement.
var cancelFunc context.CancelFunc
ctx, cancelFunc = context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout)
defer cancelFunc()
}
if c.db.GetConfig().PrepareTimeout > 0 {
// DO NOT USE cancel function in prepare statement.
var cancelFunc context.CancelFunc
ctx, cancelFunc = context.WithTimeout(ctx, c.db.GetConfig().PrepareTimeout)
defer cancelFunc()
}
// Link execution.
var out DoCommitOutput
out, err = c.db.DoCommit(ctx, DoCommitInput{
Link: link,
Sql: sql,
Type: SqlTypePrepareContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return nil, err
}
return out.Stmt, err
// Link execution.
var out DoCommitOutput
out, err = c.db.DoCommit(ctx, DoCommitInput{
Link: link,
Sql: sql,
Type: SqlTypePrepareContext,
IsTransaction: link.IsTransaction(),
})
if err != nil {
return nil, err
}
return out.Stmt, err
}
// FormatUpsert formats and returns SQL clause part for upsert statement.
// In default implements, this function performs upsert statement for MySQL like:
// `INSERT INTO ... ON DUPLICATE KEY UPDATE x=VALUES(z),m=VALUES(y)...`
func (c *Core) FormatUpsert(columns []string, list List, option DoInsertOption) (string, error) {
var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case Raw, *Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
c.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(k),
c.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's `SAVE` operation, do not automatically update the creating time.
if c.IsSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(column),
c.QuoteWord(column),
)
}
}
var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case Raw, *Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
c.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(k),
c.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's `SAVE` operation, do not automatically update the creating time.
if c.IsSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(column),
c.QuoteWord(column),
)
}
}
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr, nil
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr, nil
}
// RowsToResult converts underlying data record type sql.Rows to Result type.
func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error) {
if rows == nil {
return nil, nil
}
defer func() {
if err := rows.Close(); err != nil {
intlog.Errorf(ctx, `%+v`, err)
}
}()
if !rows.Next() {
return nil, nil
}
// Column names and types.
columnTypes, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
if rows == nil {
return nil, nil
}
defer func() {
if err := rows.Close(); err != nil {
intlog.Errorf(ctx, `%+v`, err)
}
}()
if !rows.Next() {
return nil, nil
}
// Column names and types.
columnTypes, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
if len(columnTypes) > 0 {
if internalData := c.getInternalColumnFromCtx(ctx); internalData != nil {
internalData.FirstResultColumn = columnTypes[0].Name()
}
}
var (
values = make([]interface{}, len(columnTypes))
result = make(Result, 0)
scanArgs = make([]interface{}, len(values))
)
for i := range values {
scanArgs[i] = &values[i]
}
for {
if err = rows.Scan(scanArgs...); err != nil {
return result, err
}
record := Record{}
for i, value := range values {
if value == nil {
// DO NOT use `gvar.New(nil)` here as it creates an initialized object
// which will cause struct converting issue.
record[columnTypes[i].Name()] = nil
} else {
var (
convertedValue interface{}
columnType = columnTypes[i]
localType = LocalTypeUndefined
)
if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnType); err != nil {
return nil, err
}
localType, err = c.db.CheckLocalTypeForField(ctx, columnType.DatabaseTypeName(), value)
if err != nil {
return nil, err
}
record[columnTypes[i].Name()] = NewValueWithType(convertedValue, localType)
}
}
result = append(result, record)
if !rows.Next() {
break
}
}
return result, nil
if len(columnTypes) > 0 {
if internalData := c.getInternalColumnFromCtx(ctx); internalData != nil {
internalData.FirstResultColumn = columnTypes[0].Name()
}
}
var (
values = make([]interface{}, len(columnTypes))
result = make(Result, 0)
scanArgs = make([]interface{}, len(values))
)
for i := range values {
scanArgs[i] = &values[i]
}
for {
if err = rows.Scan(scanArgs...); err != nil {
return result, err
}
record := Record{}
for i, value := range values {
if value == nil {
// DO NOT use `gvar.New(nil)` here as it creates an initialized object
// which will cause struct converting issue.
record[columnTypes[i].Name()] = nil
} else {
var (
convertedValue interface{}
columnType = columnTypes[i]
localType = LocalTypeUndefined
)
if convertedValue, err = c.columnValueToLocalValue(ctx, value, columnType); err != nil {
return nil, err
}
localType, err = c.db.CheckLocalTypeForField(ctx, columnType.DatabaseTypeName(), value)
if err != nil {
return nil, err
}
record[columnTypes[i].Name()] = NewValueWithType(convertedValue, localType)
}
}
result = append(result, record)
if !rows.Next() {
break
}
}
return result, nil
}
// OrderRandomFunction returns the SQL function for random ordering.
func (c *Core) OrderRandomFunction() string {
return "RAND()"
return "RAND()"
}
func (c *Core) columnValueToLocalValue(
ctx context.Context, value interface{}, columnType *sql.ColumnType,
ctx context.Context, value interface{}, columnType *sql.ColumnType,
) (interface{}, error) {
var scanType = columnType.ScanType()
if scanType != nil {
// Common basic builtin types.
switch scanType.Kind() {
case
reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return gconv.Convert(
gconv.String(value),
columnType.ScanType().String(),
), nil
default:
}
}
// Other complex types, especially custom types.
return c.db.ConvertValueForLocal(ctx, columnType.DatabaseTypeName(), value)
var scanType = columnType.ScanType()
if scanType != nil {
// Common basic builtin types.
switch scanType.Kind() {
case
reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return gconv.Convert(
gconv.String(value),
columnType.ScanType().String(),
), nil
default:
}
}
// Other complex types, especially custom types.
return c.db.ConvertValueForLocal(ctx, columnType.DatabaseTypeName(), value)
}

View File

@ -7,199 +7,199 @@
package gdb
import (
"database/sql"
"math"
"database/sql"
"math"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/util/gconv"
)
// IsEmpty checks and returns whether `r` is empty.
func (r Result) IsEmpty() bool {
return r == nil || r.Len() == 0
return r == nil || r.Len() == 0
}
// Len returns the length of result list.
func (r Result) Len() int {
return len(r)
return len(r)
}
// Size is alias of function Len.
func (r Result) Size() int {
return r.Len()
return r.Len()
}
// Chunk splits a Result into multiple Results,
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (r Result) Chunk(size int) []Result {
if size < 1 {
return nil
}
length := len(r)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n []Result
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, r[i*size:end])
i++
}
return n
if size < 1 {
return nil
}
length := len(r)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n []Result
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, r[i*size:end])
i++
}
return n
}
// Json converts `r` to JSON format content.
func (r Result) Json() string {
content, _ := gjson.New(r.List()).ToJsonString()
return content
content, _ := gjson.New(r.List()).ToJsonString()
return content
}
// Xml converts `r` to XML format content.
func (r Result) Xml(rootTag ...string) string {
content, _ := gjson.New(r.List()).ToXmlString(rootTag...)
return content
content, _ := gjson.New(r.List()).ToXmlString(rootTag...)
return content
}
// List converts `r` to a List.
func (r Result) List() List {
list := make(List, len(r))
for k, v := range r {
list[k] = v.Map()
}
return list
list := make(List, len(r))
for k, v := range r {
list[k] = v.Map()
}
return list
}
// Array retrieves and returns specified column values as slice.
// The parameter `field` is optional is the column field is only one.
// The default `field` is the first field name of the first item in `Result` if parameter `field` is not given.
func (r Result) Array(field ...string) []Value {
array := make([]Value, len(r))
if len(r) == 0 {
return array
}
key := ""
if len(field) > 0 && field[0] != "" {
key = field[0]
} else {
for k := range r[0] {
key = k
break
}
}
for k, v := range r {
array[k] = v[key]
}
return array
array := make([]Value, len(r))
if len(r) == 0 {
return array
}
key := ""
if len(field) > 0 && field[0] != "" {
key = field[0]
} else {
for k := range r[0] {
key = k
break
}
}
for k, v := range r {
array[k] = v[key]
}
return array
}
// MapKeyValue converts `r` to a map[string]Value of which key is specified by `key`.
// Note that the item value may be type of slice.
func (r Result) MapKeyValue(key string) map[string]Value {
var (
s string
m = make(map[string]Value)
tempMap = make(map[string][]interface{})
localType LocalType
hasMultiValues bool
)
for _, item := range r {
if k, ok := item[key]; ok {
s = k.String()
localType = k.LocalType()
tempMap[s] = append(tempMap[s], item)
if len(tempMap[s]) > 1 {
hasMultiValues = true
}
}
}
for k, v := range tempMap {
if hasMultiValues {
m[k] = NewValue(v)
} else {
m[k] = NewValueWithType(v[0], localType)
}
}
return m
var (
s string
m = make(map[string]Value)
tempMap = make(map[string][]interface{})
localType LocalType
hasMultiValues bool
)
for _, item := range r {
if k, ok := item[key]; ok {
s = k.String()
localType = k.LocalType()
tempMap[s] = append(tempMap[s], item)
if len(tempMap[s]) > 1 {
hasMultiValues = true
}
}
}
for k, v := range tempMap {
if hasMultiValues {
m[k] = NewValue(v)
} else {
m[k] = NewValueWithType(v[0], localType)
}
}
return m
}
// MapKeyStr converts `r` to a map[string]Map of which key is specified by `key`.
func (r Result) MapKeyStr(key string) map[string]Map {
m := make(map[string]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item.Map()
}
}
return m
m := make(map[string]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item.Map()
}
}
return m
}
// MapKeyInt converts `r` to a map[int]Map of which key is specified by `key`.
func (r Result) MapKeyInt(key string) map[int]Map {
m := make(map[int]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item.Map()
}
}
return m
m := make(map[int]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item.Map()
}
}
return m
}
// MapKeyUint converts `r` to a map[uint]Map of which key is specified by `key`.
func (r Result) MapKeyUint(key string) map[uint]Map {
m := make(map[uint]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item.Map()
}
}
return m
m := make(map[uint]Map)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item.Map()
}
}
return m
}
// RecordKeyStr converts `r` to a map[string]Record of which key is specified by `key`.
func (r Result) RecordKeyStr(key string) map[string]Record {
m := make(map[string]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item
}
}
return m
m := make(map[string]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.String()] = item
}
}
return m
}
// RecordKeyInt converts `r` to a map[int]Record of which key is specified by `key`.
func (r Result) RecordKeyInt(key string) map[int]Record {
m := make(map[int]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item
}
}
return m
m := make(map[int]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Int()] = item
}
}
return m
}
// RecordKeyUint converts `r` to a map[uint]Record of which key is specified by `key`.
func (r Result) RecordKeyUint(key string) map[uint]Record {
m := make(map[uint]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item
}
}
return m
m := make(map[uint]Record)
for _, item := range r {
if v, ok := item[key]; ok {
m[v.Uint()] = item
}
}
return m
}
// Structs converts `r` to struct slice.
// Note that the parameter `pointer` should be type of *[]struct/*[]*struct.
func (r Result) Structs(pointer interface{}) (err error) {
// If the result is empty and the target pointer is not empty, it returns error.
if r.IsEmpty() {
if !empty.IsEmpty(pointer, true) {
return sql.ErrNoRows
}
return nil
}
return gconv.StructsTag(r, pointer, OrmTagForStruct)
// If the result is empty and the target pointer is not empty, it returns error.
if r.IsEmpty() {
if !empty.IsEmpty(pointer, true) {
return sql.ErrNoRows
}
return nil
}
return gconv.StructsTag(r, pointer, OrmTagForStruct)
}

View File

@ -10,10 +10,10 @@ import "github.com/gogf/gf/v2/database/gdb/internal/fieldvar"
// NewValue creates and returns a new Value object.
func NewValue(value any) Value {
return fieldvar.New(value)
return fieldvar.New(value)
}
// NewValueWithType creates and returns a new Value object with specified local type.
func NewValueWithType(value any, localType LocalType) Value {
return fieldvar.NewWithType(value, localType)
return fieldvar.NewWithType(value, localType)
}

View File

@ -7,93 +7,93 @@
package fieldvar
import (
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gconv"
)
// Val returns the current value of `v`.
func (v *Var) Val() interface{} {
if v == nil {
return nil
}
return v.value
if v == nil {
return nil
}
return v.value
}
// Interface is alias of Val.
func (v *Var) Interface() interface{} {
return v.Val()
return v.Val()
}
// Bytes converts and returns `v` as []byte.
func (v *Var) Bytes() []byte {
return gconv.Bytes(v.Val())
return gconv.Bytes(v.Val())
}
// String converts and returns `v` as string.
func (v *Var) String() string {
return gconv.String(v.Val())
return gconv.String(v.Val())
}
// Bool converts and returns `v` as bool.
func (v *Var) Bool() bool {
return gconv.Bool(v.Val())
return gconv.Bool(v.Val())
}
// Int converts and returns `v` as int.
func (v *Var) Int() int {
return gconv.Int(v.Val())
return gconv.Int(v.Val())
}
// Int8 converts and returns `v` as int8.
func (v *Var) Int8() int8 {
return gconv.Int8(v.Val())
return gconv.Int8(v.Val())
}
// Int16 converts and returns `v` as int16.
func (v *Var) Int16() int16 {
return gconv.Int16(v.Val())
return gconv.Int16(v.Val())
}
// Int32 converts and returns `v` as int32.
func (v *Var) Int32() int32 {
return gconv.Int32(v.Val())
return gconv.Int32(v.Val())
}
// Int64 converts and returns `v` as int64.
func (v *Var) Int64() int64 {
return gconv.Int64(v.Val())
return gconv.Int64(v.Val())
}
// Uint converts and returns `v` as uint.
func (v *Var) Uint() uint {
return gconv.Uint(v.Val())
return gconv.Uint(v.Val())
}
// Uint8 converts and returns `v` as uint8.
func (v *Var) Uint8() uint8 {
return gconv.Uint8(v.Val())
return gconv.Uint8(v.Val())
}
// Uint16 converts and returns `v` as uint16.
func (v *Var) Uint16() uint16 {
return gconv.Uint16(v.Val())
return gconv.Uint16(v.Val())
}
// Uint32 converts and returns `v` as uint32.
func (v *Var) Uint32() uint32 {
return gconv.Uint32(v.Val())
return gconv.Uint32(v.Val())
}
// Uint64 converts and returns `v` as uint64.
func (v *Var) Uint64() uint64 {
return gconv.Uint64(v.Val())
return gconv.Uint64(v.Val())
}
// Float32 converts and returns `v` as float32.
func (v *Var) Float32() float32 {
return gconv.Float32(v.Val())
return gconv.Float32(v.Val())
}
// Float64 converts and returns `v` as float64.
func (v *Var) Float64() float64 {
return gconv.Float64(v.Val())
return gconv.Float64(v.Val())
}

View File

@ -7,24 +7,24 @@
package fieldvar
import (
"github.com/gogf/gf/v2/internal/deepcopy"
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/internal/deepcopy"
"github.com/gogf/gf/v2/util/gutil"
)
// Copy does a deep copy of current Var and returns a pointer to this Var.
func (v *Var) Copy() *Var {
return NewWithType(gutil.Copy(v.Val()), v.localType)
return NewWithType(gutil.Copy(v.Val()), v.localType)
}
// Clone does a shallow copy of current Var and returns a pointer to this Var.
func (v *Var) Clone() *Var {
return NewWithType(v.Val(), v.localType)
return NewWithType(v.Val(), v.localType)
}
// DeepCopy implements interface for deep copy of current type.
func (v *Var) DeepCopy() interface{} {
if v == nil {
return nil
}
return NewWithType(deepcopy.Copy(v.Val()), v.localType)
if v == nil {
return nil
}
return NewWithType(deepcopy.Copy(v.Val()), v.localType)
}

View File

@ -13,53 +13,53 @@ type MapOption = gconv.MapOption
// Map converts and returns `v` as map[string]interface{}.
func (v *Var) Map(option ...MapOption) map[string]interface{} {
return gconv.Map(v.Val(), option...)
return gconv.Map(v.Val(), option...)
}
// MapStrAny is like function Map, but implements the interface of MapStrAny.
func (v *Var) MapStrAny(option ...MapOption) map[string]interface{} {
return v.Map(option...)
return v.Map(option...)
}
// MapStrStr converts and returns `v` as map[string]string.
func (v *Var) MapStrStr(option ...MapOption) map[string]string {
return gconv.MapStrStr(v.Val(), option...)
return gconv.MapStrStr(v.Val(), option...)
}
// MapStrVar converts and returns `v` as map[string]Var.
func (v *Var) MapStrVar(option ...MapOption) map[string]*Var {
m := v.Map(option...)
if len(m) > 0 {
vMap := make(map[string]*Var, len(m))
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
m := v.Map(option...)
if len(m) > 0 {
vMap := make(map[string]*Var, len(m))
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Maps converts and returns `v` as map[string]string.
// See gconv.Maps.
func (v *Var) Maps(option ...MapOption) []map[string]interface{} {
return gconv.Maps(v.Val(), option...)
return gconv.Maps(v.Val(), option...)
}
// MapToMap converts any map type variable `params` to another map type variable `pointer`.
// See gconv.MapToMap.
func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMap(v.Val(), pointer, mapping...)
return gconv.MapToMap(v.Val(), pointer, mapping...)
}
// MapToMaps converts any map type variable `params` to another map type variable `pointer`.
// See gconv.MapToMaps.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep converts any map type variable `params` to another map type variable
// `pointer` recursively.
// See gconv.MapToMapsDeep.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}

View File

@ -7,80 +7,80 @@
package fieldvar
import (
"github.com/gogf/gf/v2/database/gdb/internal/defines"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/database/gdb/internal/defines"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/util/gconv"
)
// Ints converts and returns `v` as []int.
func (v *Var) Ints() []int {
return gconv.Ints(v.Val())
return gconv.Ints(v.Val())
}
// Int64s converts and returns `v` as []int64.
func (v *Var) Int64s() []int64 {
return gconv.Int64s(v.Val())
return gconv.Int64s(v.Val())
}
// Uints converts and returns `v` as []uint.
func (v *Var) Uints() []uint {
return gconv.Uints(v.Val())
return gconv.Uints(v.Val())
}
// Uint64s converts and returns `v` as []uint64.
func (v *Var) Uint64s() []uint64 {
return gconv.Uint64s(v.Val())
return gconv.Uint64s(v.Val())
}
// Floats is alias of Float64s.
func (v *Var) Floats() []float64 {
return gconv.Floats(v.Val())
return gconv.Floats(v.Val())
}
// Float32s converts and returns `v` as []float32.
func (v *Var) Float32s() []float32 {
return gconv.Float32s(v.Val())
return gconv.Float32s(v.Val())
}
// Float64s converts and returns `v` as []float64.
func (v *Var) Float64s() []float64 {
return gconv.Float64s(v.Val())
return gconv.Float64s(v.Val())
}
// Strings converts and returns `v` as []string.
func (v *Var) Strings() []string {
if v.localType == defines.LocalTypeJson {
var s []string
_ = json.Unmarshal(v.Bytes(), &s)
return s
}
return gconv.Strings(v.Val())
if v.localType == defines.LocalTypeJson {
var s []string
_ = json.Unmarshal(v.Bytes(), &s)
return s
}
return gconv.Strings(v.Val())
}
// Interfaces converts and returns `v` as []interfaces{}.
func (v *Var) Interfaces() []interface{} {
return gconv.Interfaces(v.Val())
return gconv.Interfaces(v.Val())
}
// Slice is alias of Interfaces.
func (v *Var) Slice() []interface{} {
return v.Interfaces()
return v.Interfaces()
}
// Array is alias of Interfaces.
func (v *Var) Array() []interface{} {
return v.Interfaces()
return v.Interfaces()
}
// Vars converts and returns `v` as []Var.
func (v *Var) Vars() []*Var {
array := gconv.Interfaces(v.Val())
if len(array) == 0 {
return nil
}
vars := make([]*Var, len(array))
for k, v := range array {
vars[k] = New(v)
}
return vars
array := gconv.Interfaces(v.Val())
if len(array) == 0 {
return nil
}
vars := make([]*Var, len(array))
for k, v := range array {
vars[k] = New(v)
}
return vars
}

View File

@ -7,17 +7,17 @@
package fieldvar
import (
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gconv"
)
// Struct maps value of `v` to `pointer`.
// The parameter `pointer` should be a pointer to a struct instance.
// The parameter `mapping` is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Structs converts and returns `v` as given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
return gconv.Structs(v.Val(), pointer, mapping...)
return gconv.Structs(v.Val(), pointer, mapping...)
}

View File

@ -7,28 +7,28 @@
package fieldvar
import (
"time"
"time"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
// Time converts and returns `v` as time.Time.
// The parameter `format` specifies the format of the time string using gtime,
// Example: Y-m-d H:i:s.
func (v *Var) Time(format ...string) time.Time {
return gconv.Time(v.Val(), format...)
return gconv.Time(v.Val(), format...)
}
// Duration converts and returns `v` as time.Duration.
// If value of `v` is string, then it uses time.ParseDuration for conversion.
func (v *Var) Duration() time.Duration {
return gconv.Duration(v.Val())
return gconv.Duration(v.Val())
}
// GTime converts and returns `v` as *gtime.Time.
// The parameter `format` specifies the format of the time string using gtime,
// Example: Y-m-d H:i:s.
func (v *Var) GTime(format ...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
return gconv.GTime(v.Val(), format...)
}