Skip to content

Commit 3373418

Browse files
committed
Use go:linkname to call convertAssign instead of copying it
1 parent ee4e3e2 commit 3373418

1 file changed

Lines changed: 8 additions & 146 deletions

File tree

reflectx/reflect.go

Lines changed: 8 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
package reflectx
88

99
import (
10-
"database/sql"
11-
"fmt"
1210
"reflect"
1311
"runtime"
14-
"strconv"
1512
"strings"
1613
"sync"
14+
_ "unsafe"
1715
)
1816

1917
// A FieldInfo is metadata for a struct field.
@@ -224,17 +222,15 @@ func (o *ObjectContext) NewRow(value reflect.Value) {
224222
// a nestedFieldScanner is returned instead of the standard value reference
225223
func (o *ObjectContext) FieldForIndexes(indexes []int) reflect.Value {
226224
if len(indexes) == 1 {
227-
val := FieldByIndexes(o.value, indexes)
228-
return val
225+
return FieldByIndexes(o.value, indexes)
229226
}
230227

231228
obj := &nestedFieldScanner{
232229
parent: o,
233230
indexes: indexes,
234231
}
235232

236-
v := reflect.ValueOf(obj).Elem()
237-
return v
233+
return reflect.ValueOf(obj).Elem()
238234
}
239235

240236
// nestedFieldScanner will only forward the Scan to the nested value if
@@ -245,149 +241,16 @@ type nestedFieldScanner struct {
245241
}
246242

247243
// Scan implements sql.Scanner.
248-
// This method largely mirrors the sql.convertAssign() method with some minor changes
249244
func (o *nestedFieldScanner) Scan(src interface{}) error {
250245
if src == nil {
251246
return nil
252247
}
253-
254-
dv := FieldByIndexes(o.parent.value, o.indexes)
255-
// Dereference pointer fields to avoid double pointers **T
256-
if dv.Kind() == reflect.Pointer {
257-
dv.Set(reflect.New(dv.Type().Elem()))
258-
dv = dv.Elem()
259-
}
260-
iface := dv.Addr().Interface()
261-
262-
if scan, ok := iface.(sql.Scanner); ok {
263-
return scan.Scan(src)
264-
}
265-
266-
sv := reflect.ValueOf(src)
267-
268-
// below is taken from https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/database/sql/convert.go
269-
// with a few minor edits
270-
271-
if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) {
272-
switch b := src.(type) {
273-
case []byte:
274-
dv.Set(reflect.ValueOf(bytesClone(b)))
275-
default:
276-
dv.Set(sv)
277-
}
278-
279-
return nil
280-
}
281-
282-
if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) {
283-
dv.Set(sv.Convert(dv.Type()))
284-
return nil
285-
}
286-
287-
// The following conversions use a string value as an intermediate representation
288-
// to convert between various numeric types.
289-
//
290-
// This also allows scanning into user defined types such as "type Int int64".
291-
// For symmetry, also check for string destination types.
292-
switch dv.Kind() {
293-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
294-
if src == nil {
295-
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
296-
}
297-
s := asString(src)
298-
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
299-
if err != nil {
300-
err = strconvErr(err)
301-
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
302-
}
303-
dv.SetInt(i64)
304-
return nil
305-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
306-
if src == nil {
307-
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
308-
}
309-
s := asString(src)
310-
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
311-
if err != nil {
312-
err = strconvErr(err)
313-
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
314-
}
315-
dv.SetUint(u64)
316-
return nil
317-
case reflect.Float32, reflect.Float64:
318-
if src == nil {
319-
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
320-
}
321-
s := asString(src)
322-
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
323-
if err != nil {
324-
err = strconvErr(err)
325-
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
326-
}
327-
dv.SetFloat(f64)
328-
return nil
329-
case reflect.String:
330-
if src == nil {
331-
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
332-
}
333-
switch v := src.(type) {
334-
case string:
335-
dv.SetString(v)
336-
return nil
337-
case []byte:
338-
dv.SetString(string(v))
339-
return nil
340-
}
341-
}
342-
343-
return fmt.Errorf("don't know how to parse type %T -> %T", src, iface)
248+
dest := FieldByIndexes(o.parent.value, o.indexes)
249+
return convertAssign(dest.Addr().Interface(), src)
344250
}
345251

346-
// returns internal conversion error if available
347-
// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/database/sql/convert.go
348-
func strconvErr(err error) error {
349-
if ne, ok := err.(*strconv.NumError); ok {
350-
return ne.Err
351-
}
352-
return err
353-
}
354-
355-
// converts value to it's string value
356-
// taken from https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/database/sql/convert.go
357-
func asString(src interface{}) string {
358-
switch v := src.(type) {
359-
case string:
360-
return v
361-
case []byte:
362-
return string(v)
363-
}
364-
rv := reflect.ValueOf(src)
365-
switch rv.Kind() {
366-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
367-
return strconv.FormatInt(rv.Int(), 10)
368-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
369-
return strconv.FormatUint(rv.Uint(), 10)
370-
case reflect.Float64:
371-
return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
372-
case reflect.Float32:
373-
return strconv.FormatFloat(rv.Float(), 'g', -1, 32)
374-
case reflect.Bool:
375-
return strconv.FormatBool(rv.Bool())
376-
}
377-
return fmt.Sprintf("%v", src)
378-
}
379-
380-
// bytesClone returns a copy of b[:len(b)].
381-
// The result may have additional unused capacity.
382-
// Clone(nil) returns nil.
383-
//
384-
// bytesClone is a mirror of bytes.Clone while our go.mod is on an older version
385-
func bytesClone(b []byte) []byte {
386-
if b == nil {
387-
return nil
388-
}
389-
return append([]byte{}, b...)
390-
}
252+
//go:linkname convertAssign database/sql.convertAssign
253+
func convertAssign(dest, src interface{}) error
391254

392255
// FieldByIndexes returns a value for the field given by the struct traversal
393256
// for the given value.
@@ -396,8 +259,7 @@ func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
396259
v = reflect.Indirect(v).Field(i)
397260
// if this is a pointer and it's nil, allocate a new value and set it
398261
if v.Kind() == reflect.Ptr && v.IsNil() {
399-
alloc := reflect.New(Deref(v.Type()))
400-
v.Set(alloc)
262+
v.Set(reflect.New(v.Type().Elem()))
401263
}
402264
if v.Kind() == reflect.Map && v.IsNil() {
403265
v.Set(reflect.MakeMap(v.Type()))

0 commit comments

Comments
 (0)