v0.0.44
This commit is contained in:
201
sq/structscanner.go
Normal file
201
sq/structscanner.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package sq
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// forked from sqlx, but added ability to unmarshal optional-nested structs
|
||||
|
||||
type StructScanner struct {
|
||||
rows *sqlx.Rows
|
||||
Mapper *reflectx.Mapper
|
||||
unsafe bool
|
||||
|
||||
fields [][]int
|
||||
values []any
|
||||
columns []string
|
||||
}
|
||||
|
||||
func NewStructScanner(rows *sqlx.Rows, unsafe bool) *StructScanner {
|
||||
return &StructScanner{
|
||||
rows: rows,
|
||||
Mapper: reflectx.NewMapper("db"),
|
||||
unsafe: unsafe,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *StructScanner) Start(dest any) error {
|
||||
v := reflect.ValueOf(dest)
|
||||
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return errors.New("must pass a pointer, not a value, to StructScan destination")
|
||||
}
|
||||
|
||||
columns, err := r.rows.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.columns = columns
|
||||
r.fields = r.Mapper.TraversalsByName(v.Type(), columns)
|
||||
// if we are not unsafe and are missing fields, return an error
|
||||
if f, err := missingFields(r.fields); err != nil && !r.unsafe {
|
||||
return fmt.Errorf("missing destination name %s in %T", columns[f], dest)
|
||||
}
|
||||
r.values = make([]interface{}, len(columns))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StructScan forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
// does also wok with nullabel structs (from LEFT JOIN's)
|
||||
func (r *StructScanner) StructScanExt(dest any) error {
|
||||
v := reflect.ValueOf(dest)
|
||||
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return errors.New("must pass a pointer, not a value, to StructScan destination")
|
||||
}
|
||||
|
||||
// ========= STEP 1 :: =========
|
||||
|
||||
v = v.Elem()
|
||||
|
||||
err := fieldsByTraversal(v, r.fields, r.values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// scan into the struct field pointers and append to our results
|
||||
err = r.rows.Scan(r.values...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nullStructs := make(map[string]bool)
|
||||
|
||||
for i, traversal := range r.fields {
|
||||
if len(traversal) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
isnsil := reflect.ValueOf(r.values[i]).Elem().IsNil()
|
||||
|
||||
for i := 1; i < len(traversal); i++ {
|
||||
|
||||
canParentNil := reflectx.FieldByIndexes(v, traversal[0:i]).Kind() == reflect.Pointer
|
||||
|
||||
k := fmt.Sprintf("%v", traversal[0:i])
|
||||
if v, ok := nullStructs[k]; ok {
|
||||
|
||||
nullStructs[k] = canParentNil && v && isnsil
|
||||
|
||||
} else {
|
||||
nullStructs[k] = canParentNil && isnsil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
forcenulled := make(map[string]bool)
|
||||
|
||||
for i, traversal := range r.fields {
|
||||
if len(traversal) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
anyparentnull := false
|
||||
for i := 1; i < len(traversal); i++ {
|
||||
k := fmt.Sprintf("%v", traversal[0:i])
|
||||
if nv, ok := nullStructs[k]; ok && nv {
|
||||
|
||||
if _, ok := forcenulled[k]; !ok {
|
||||
f := reflectx.FieldByIndexes(v, traversal[0:i])
|
||||
f.Set(reflect.New(f.Type().Elem())) // set to nil
|
||||
forcenulled[k] = true
|
||||
}
|
||||
|
||||
anyparentnull = true
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if anyparentnull {
|
||||
continue
|
||||
}
|
||||
|
||||
f := reflectx.FieldByIndexes(v, traversal)
|
||||
|
||||
val1 := reflect.ValueOf(r.values[i])
|
||||
val2 := val1.Elem()
|
||||
val3 := val2.Elem()
|
||||
|
||||
if val2.IsNil() {
|
||||
if f.Kind() != reflect.Pointer {
|
||||
return errors.New(fmt.Sprintf("Cannot set field %v to NULL value from column '%s' (type: %s)", traversal, r.columns[i], f.Type().String()))
|
||||
}
|
||||
|
||||
f.Set(reflect.New(f.Type().Elem())) // set to nil
|
||||
} else {
|
||||
f.Set(val3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return r.rows.Err()
|
||||
}
|
||||
|
||||
// StructScan forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
// without (relevant) changes
|
||||
func (r *StructScanner) StructScanBase(dest any) error {
|
||||
v := reflect.ValueOf(dest)
|
||||
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return errors.New("must pass a pointer, not a value, to StructScan destination")
|
||||
}
|
||||
|
||||
v = v.Elem()
|
||||
|
||||
err := fieldsByTraversal(v, r.fields, r.values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// scan into the struct field pointers and append to our results
|
||||
err = r.rows.Scan(r.values...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.rows.Err()
|
||||
}
|
||||
|
||||
// fieldsByTraversal forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}) error {
|
||||
v = reflect.Indirect(v)
|
||||
if v.Kind() != reflect.Struct {
|
||||
return errors.New("argument not a struct")
|
||||
}
|
||||
|
||||
for i, traversal := range traversals {
|
||||
if len(traversal) == 0 {
|
||||
values[i] = new(interface{})
|
||||
continue
|
||||
}
|
||||
f := reflectx.FieldByIndexes(v, traversal)
|
||||
|
||||
values[i] = reflect.New(reflect.PointerTo(f.Type())).Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// missingFields forked from github.com/jmoiron/sqlx@v1.3.5/sqlx.go
|
||||
func missingFields(transversals [][]int) (field int, err error) {
|
||||
for i, t := range transversals {
|
||||
if len(t) == 0 {
|
||||
return i, errors.New("missing field")
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
Reference in New Issue
Block a user