diff --git a/scan.go b/scan.go index f28440a3..462d42c7 100644 --- a/scan.go +++ b/scan.go @@ -55,13 +55,15 @@ func (db *DB) scanIntoStruct(sch *schema.Schema, rows *sql.Rows, reflectValue re if sch == nil { values[idx] = reflectValue.Interface() } else if field := sch.LookUpField(column); field != nil && field.Readable { - values[idx] = field.NewScanValue() - defer field.ReleaseScanValue(values[idx]) + fieldValue := field.NewValuePool.Get() + values[idx] = &fieldValue + defer field.NewValuePool.Put(fieldValue) } else if names := strings.Split(column, "__"); len(names) > 1 { if rel, ok := sch.Relationships.Relations[names[0]]; ok { if field := rel.FieldSchema.LookUpField(strings.Join(names[1:], "__")); field != nil && field.Readable { - values[idx] = field.NewScanValue() - defer field.ReleaseScanValue(values[idx]) + fieldValue := field.NewValuePool.Get() + values[idx] = &fieldValue + defer field.NewValuePool.Put(fieldValue) continue } } @@ -85,12 +87,12 @@ func (db *DB) scanIntoStruct(sch *schema.Schema, rows *sql.Rows, reflectValue re if rel, ok := sch.Relationships.Relations[names[0]]; ok { if field := rel.FieldSchema.LookUpField(strings.Join(names[1:], "__")); field != nil && field.Readable { relValue := rel.Field.ReflectValueOf(db.Statement.Context, reflectValue) - value := reflect.ValueOf(values[idx]).Elem() if relValue.Kind() == reflect.Ptr && relValue.IsNil() { - if value.IsNil() { + if value := reflect.ValueOf(values[idx]).Elem(); value.Kind() == reflect.Ptr && value.IsNil() { continue } + relValue.Set(reflect.New(relValue.Type().Elem())) } diff --git a/schema/field.go b/schema/field.go index 9cd840cb..b2f48a6a 100644 --- a/schema/field.go +++ b/schema/field.go @@ -84,8 +84,7 @@ type Field struct { ReflectValueOf func(context.Context, reflect.Value) reflect.Value ValueOf func(context.Context, reflect.Value) (value interface{}, zero bool) Set func(context.Context, reflect.Value, interface{}) error - NewScanValue func() interface{} - ReleaseScanValue func(interface{}) + NewValuePool FieldNewValuePool } // ParseField parses reflect.StructField to Field @@ -415,31 +414,68 @@ func (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field { } var ( - bytesPool = sync.Pool{ + stringPool = &sync.Pool{ New: func() interface{} { - bs := make([]byte, 0, 10) - return &bs + var v string + return &v }, } - bytesPoolReleaser = func(v interface{}) { - bs := v.(*[]byte) - *bs = (*bs)[:0] - bytesPool.Put(bs) + intPool = &sync.Pool{ + New: func() interface{} { + var v int64 + return &v + }, + } + uintPool = &sync.Pool{ + New: func() interface{} { + var v uint64 + return &v + }, + } + floatPool = &sync.Pool{ + New: func() interface{} { + var v float64 + return &v + }, + } + boolPool = &sync.Pool{ + New: func() interface{} { + var v bool + return &v + }, + } + timePool = &sync.Pool{ + New: func() interface{} { + return &time.Time{} + }, } ) // create valuer, setter when parse struct func (field *Field) setupValuerAndSetter() { - // Setup NewScanValue + // Setup NewValuePool switch field.IndirectFieldType.Kind() { case reflect.String: - field.NewScanValue = bytesPool.Get - field.ReleaseScanValue = bytesPoolReleaser + field.NewValuePool = stringPool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + field.NewValuePool = intPool + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + field.NewValuePool = uintPool + case reflect.Float32, reflect.Float64: + field.NewValuePool = floatPool + case reflect.Bool: + field.NewValuePool = boolPool default: - field.NewScanValue = func() interface{} { - return reflect.New(reflect.PtrTo(field.IndirectFieldType)).Interface() + if field.IndirectFieldType == TimeReflectType { + field.NewValuePool = timePool } - field.ReleaseScanValue = func(interface{}) { + if field.NewValuePool == nil { + field.NewValuePool = fieldNewValuePool{ + getter: func() interface{} { + return reflect.New(reflect.PtrTo(field.IndirectFieldType)).Interface() + }, + putter: func(interface{}) {}, + } } } @@ -546,9 +582,9 @@ func (field *Field) setupValuerAndSetter() { switch data := v.(type) { case bool: field.ReflectValueOf(ctx, value).SetBool(data) - case *bool: + case **bool: if data != nil { - field.ReflectValueOf(ctx, value).SetBool(*data) + field.ReflectValueOf(ctx, value).SetBool(**data) } else { field.ReflectValueOf(ctx, value).SetBool(false) } @@ -565,6 +601,10 @@ func (field *Field) setupValuerAndSetter() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) { switch data := v.(type) { + case **int64: + if data != nil { + field.ReflectValueOf(ctx, value).SetInt(**data) + } case int64: field.ReflectValueOf(ctx, value).SetInt(data) case int: @@ -625,6 +665,10 @@ func (field *Field) setupValuerAndSetter() { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) { switch data := v.(type) { + case **uint64: + if data != nil { + field.ReflectValueOf(ctx, value).SetUint(**data) + } case uint64: field.ReflectValueOf(ctx, value).SetUint(data) case uint: @@ -673,6 +717,10 @@ func (field *Field) setupValuerAndSetter() { case reflect.Float32, reflect.Float64: field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) { switch data := v.(type) { + case **float64: + if data != nil { + field.ReflectValueOf(ctx, value).SetFloat(**data) + } case float64: field.ReflectValueOf(ctx, value).SetFloat(data) case float32: @@ -713,6 +761,12 @@ func (field *Field) setupValuerAndSetter() { case reflect.String: field.Set = func(ctx context.Context, value reflect.Value, v interface{}) (err error) { switch data := v.(type) { + case *string: + field.ReflectValueOf(ctx, value).SetString(*data) + case **string: + if data != nil { + field.ReflectValueOf(ctx, value).SetString(**data) + } case string: field.ReflectValueOf(ctx, value).SetString(data) case []byte: diff --git a/schema/interfaces.go b/schema/interfaces.go index ff9b0842..638b96cf 100644 --- a/schema/interfaces.go +++ b/schema/interfaces.go @@ -13,6 +13,25 @@ type GormDataTypeInterface interface { GormDataType() string } +// FieldNewValuePool field new scan value pool +type FieldNewValuePool interface { + Get() interface{} + Put(interface{}) +} + +type fieldNewValuePool struct { + getter func() interface{} + putter func(interface{}) +} + +func (fp fieldNewValuePool) Get() interface{} { + return fp.getter() +} + +func (fp fieldNewValuePool) Put(v interface{}) { + fp.putter(v) +} + // Serializer field value serializer type Serializer struct { Field *Field diff --git a/tests/go.mod b/tests/go.mod index f414a2dd..523683ba 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -9,7 +9,7 @@ require ( github.com/jinzhu/now v1.1.4 github.com/lib/pq v1.10.4 github.com/mattn/go-sqlite3 v1.14.11 // indirect - golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect gorm.io/driver/mysql v1.2.3 gorm.io/driver/postgres v1.2.3 gorm.io/driver/sqlite v1.2.6