Skip to content

Commit

Permalink
start to move over to scanAll interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jmoiron committed Sep 4, 2014
1 parent 44ed2f4 commit 8f4e2b0
Showing 1 changed file with 78 additions and 38 deletions.
116 changes: 78 additions & 38 deletions sqlx.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,13 +757,23 @@ type rowsi interface {
Scan(...interface{}) error
}

// StructScan all rows from an sql.Rows or an sqlx.Rows into the dest slice.
// StructScan will scan in the entire rows result, so if you need do not want to
// allocate structs for the entire result, use Queryx and see sqlx.Rows.StructScan.
// If rowsi is sqlx.Rows, it will use its mapper, otherwise it will use the default.
func StructScan(rows rowsi, dest interface{}) error {
// scanAll scans all rows into a destination, which must be a slice of any
// type. If the destination slice type is a Struct, then StructScan will be
// used on each row. If the destination is some other kind of base type, then
// each row must only have one column which can scan into that type. This
// allows you to do something like:
//
// rows, _ := db.Query("select id from people;")
// var ids []int
// scanAll(rows, &ids, false)
//
// and ids will be a list of the id results. I realize that this is a desirable
// interface to expose to users, but for now it will only be exposed via changes
// to `Get` and `Select`. The reason that this has been implemented like this is
// this is the only way to not duplicate reflect work in the new API while
// maintaining backwards compatibility.
func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
var v, vp reflect.Value
var isPtr bool

value := reflect.ValueOf(dest)

Expand All @@ -774,60 +784,90 @@ func StructScan(rows rowsi, dest interface{}) error {
if value.IsNil() {
return errors.New("nil pointer passed to StructScan destination")
}

direct := reflect.Indirect(value)

slice, err := baseType(value.Type(), reflect.Slice)
if err != nil {
return err
}
isPtr = slice.Elem().Kind() == reflect.Ptr
base, err := baseType(slice.Elem(), reflect.Struct)
if err != nil {
return err

isPtr := slice.Elem().Kind() == reflect.Ptr
base := reflectx.Deref(slice.Elem())
isStruct := base.Kind() == reflect.Struct

// if we must have a struct and the base type isn't a struct, return an error.
// this maintains API compatibility for StructScan, which is only important
// because StructScan should involve structs and it feels gross to add more
// weird junk to it.
if structOnly && !isStruct {
return fmt.Errorf("expected %s but got %s", reflect.Struct, base.Kind())
}

columns, err := rows.Columns()
if err != nil {
return err
}

var m *reflectx.Mapper
switch rows.(type) {
case *Rows:
m = rows.(*Rows).Mapper
default:
m = mapper()
}

fields := m.TraversalsByName(base, columns)
// if we are not unsafe and are missing fields, return an error
if f, err := missingFields(fields); err != nil && !isUnsafe(rows) {
return fmt.Errorf("missing destination name %s", columns[f])
// if it's a base type make sure it only has 1 column; if not return an error
if !isStruct && len(columns) > 1 {
return fmt.Errorf("non-struct dest type %s with >1 columns (%d)", base.Kind(), len(columns))
}
values := make([]interface{}, len(columns))

for rows.Next() {
// create a new struct type (which returns PtrTo) and indirect it
vp = reflect.New(base)
v = reflect.Indirect(vp)

err = fieldsByTraversal(v, fields, values, true)
var values []interface{}

// scan into the struct field pointers and append to our results
err = rows.Scan(values...)
if err != nil {
return err
if isStruct {
var m *reflectx.Mapper
switch rows.(type) {
case *Rows:
m = rows.(*Rows).Mapper
default:
m = mapper()
}

if isPtr {
direct.Set(reflect.Append(direct, vp))
} else {
direct.Set(reflect.Append(direct, v))
fields := m.TraversalsByName(base, columns)
// if we are not unsafe and are missing fields, return an error
if f, err := missingFields(fields); err != nil && !isUnsafe(rows) {
return fmt.Errorf("missing destination name %s", columns[f])
}
values = make([]interface{}, len(columns))

for rows.Next() {
// create a new struct type (which returns PtrTo) and indirect it
vp = reflect.New(base)
v = reflect.Indirect(vp)

err = fieldsByTraversal(v, fields, values, true)

// scan into the struct field pointers and append to our results
err = rows.Scan(values...)
if err != nil {
return err
}

if isPtr {
direct.Set(reflect.Append(direct, vp))
} else {
direct.Set(reflect.Append(direct, v))
}
}
}

return rows.Err()

}

// FIXME: StructScan was the very first bit of API in sqlx, and now unfortunately
// it doesn't really feel like it's named properly. There is an incongruency
// between this and the way that StructScan (which might better be ScanStruct
// anyway) works on a rows object.

// StructScan all rows from an sql.Rows or an sqlx.Rows into the dest slice.
// StructScan will scan in the entire rows result, so if you need do not want to
// allocate structs for the entire result, use Queryx and see sqlx.Rows.StructScan.
// If rows is sqlx.Rows, it will use its mapper, otherwise it will use the default.
func StructScan(rows rowsi, dest interface{}) error {
return scanAll(rows, dest, true)

}

// reflect helpers
Expand Down

0 comments on commit 8f4e2b0

Please sign in to comment.