Skip to content

Commit

Permalink
Fix for #90
Browse files Browse the repository at this point in the history
* When a slice that is `*sql.RawBytes`, `*[]byte` or `sql.Scanner` no errors will be returned.
  • Loading branch information
doug-martin committed Jul 11, 2019
1 parent 666771e commit 3151d9a
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 21 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## v7.1.0

* [FIXED] Embedded pointers with property names that duplicate parent struct properties. [#23](https://github.com/doug-martin/goqu/issues/23)
* [FIXED] Can't scan values using []byte or []string [#90](https://github.com/doug-martin/goqu/issues/90)
* When a slice that is `*sql.RawBytes`, `*[]byte` or `sql.Scanner` no errors will be returned.

## v7.0.1

Expand Down
10 changes: 8 additions & 2 deletions exec/query_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,19 @@ func (q QueryExecutor) ScanValContext(ctx context.Context, i interface{}) (bool,
}
val = reflect.Indirect(val)
if util.IsSlice(val.Kind()) {
return false, errScanValNonSlice
switch i.(type) {
case *sql.RawBytes: // do nothing
case *[]byte: // do nothing
case sql.Scanner: // do nothing
default:
return false, errScanValNonSlice
}
}
rows, err := q.QueryContext(ctx)
if err != nil {
return false, err
}
return NewScanner(rows).ScanVals(i)
return NewScanner(rows).ScanVal(i)
}

func (q QueryExecutor) rowsScanner(ctx context.Context) (Scanner, error) {
Expand Down
46 changes: 27 additions & 19 deletions exec/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type (
Scanner interface {
ScanStructs(i interface{}) (bool, error)
ScanVals(i interface{}) (bool, error)
ScanVal(i interface{}) (found bool, err error)
}
scanner struct {
rows *sql.Rows
Expand Down Expand Up @@ -76,28 +77,35 @@ func (q *scanner) ScanVals(i interface{}) (found bool, err error) {
defer q.rows.Close()
val := reflect.Indirect(reflect.ValueOf(i))
t, _, isSliceOfPointers := util.GetTypeInfo(i, val)
switch val.Kind() {
case reflect.Slice:
for q.rows.Next() {
found = true
row := reflect.New(t)
if err = q.rows.Scan(row.Interface()); err != nil {
return found, err
}
if isSliceOfPointers {
val.Set(reflect.Append(val, row))
} else {
val.Set(reflect.Append(val, reflect.Indirect(row)))
}
for q.rows.Next() {
found = true
row := reflect.New(t)
if err = q.rows.Scan(row.Interface()); err != nil {
return found, err
}
default:
for q.rows.Next() {
found = true
if err = q.rows.Scan(i); err != nil {
return false, err
}
if isSliceOfPointers {
val.Set(reflect.Append(val, row))
} else {
val.Set(reflect.Append(val, reflect.Indirect(row)))
}
}
return found, q.rows.Err()
}

// This will execute the SQL and append results to the slice.
// var ids []uint32
// if err := From("test").Select("id").ScanVals(&ids); err != nil{
// panic(err.Error()
// }
//
// i: Takes a pointer to a slice of primitive values.
func (q *scanner) ScanVal(i interface{}) (found bool, err error) {
defer q.rows.Close()
for q.rows.Next() {
found = true
if err = q.rows.Scan(i); err != nil {
return false, err
}
}
return found, q.rows.Err()
}
Expand Down
76 changes: 76 additions & 0 deletions exec/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exec
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"testing"

Expand Down Expand Up @@ -707,6 +708,81 @@ func (cet *crudExecTest) TestScanVal() {
assert.Equal(t, ptrID, int64(1))
}

func (cet *crudExecTest) TestScanVal_withByteSlice() {
t := cet.T()
mDb, mock, err := sqlmock.New()
assert.NoError(t, err)

mock.ExpectQuery(`SELECT "name" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"name"}).FromCSVString("byte slice result"))

db := newMockDb(mDb)
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)

var bytes []byte
found, err := e.ScanVal(bytes)
assert.Equal(t, errScanValPointer, err)
assert.False(t, found)

found, err = e.ScanVal(&bytes)
assert.NoError(t, err)
assert.True(t, found)
assert.Equal(t, []byte("byte slice result"), bytes)
}

func (cet *crudExecTest) TestScanVal_withRawBytes() {
t := cet.T()
mDb, mock, err := sqlmock.New()
assert.NoError(t, err)

mock.ExpectQuery(`SELECT "name" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"name"}).FromCSVString("byte slice result"))

db := newMockDb(mDb)
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)

var bytes sql.RawBytes
found, err := e.ScanVal(bytes)
assert.Equal(t, errScanValPointer, err)
assert.False(t, found)

found, err = e.ScanVal(&bytes)
assert.NoError(t, err)
assert.True(t, found)
assert.Equal(t, sql.RawBytes("byte slice result"), bytes)
}

type JSONBoolArray []bool

func (b *JSONBoolArray) Scan(src interface{}) error {
return json.Unmarshal(src.([]byte), b)
}

func (cet *crudExecTest) TestScanVal_withValuerSlice() {
t := cet.T()
mDb, mock, err := sqlmock.New()
assert.NoError(t, err)

mock.ExpectQuery(`SELECT "bools" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"bools"}).FromCSVString(`"[true, false, true]"`))

db := newMockDb(mDb)
e := newQueryExecutor(db, nil, `SELECT "bools" FROM "items"`)

var bools JSONBoolArray
found, err := e.ScanVal(bools)
assert.Equal(t, errScanValPointer, err)
assert.False(t, found)

found, err = e.ScanVal(&bools)
assert.NoError(t, err)
assert.True(t, found)
assert.Equal(t, JSONBoolArray{true, false, true}, bools)
}

func TestCrudExecSuite(t *testing.T) {
suite.Run(t, new(crudExecTest))
}

0 comments on commit 3151d9a

Please sign in to comment.