11package queries
22
33import (
4+ "database/sql"
45 "errors"
56 "reflect"
67 "testing"
8+ "time"
79
810 "go-simpler.org/queries/internal/assert"
911 . "go-simpler.org/queries/internal/assert/EF"
1012)
1113
1214func Test_scan (t * testing.T ) {
13- t .Run ("non-struct T " , func (t * testing.T ) {
15+ t .Run ("no columns " , func (t * testing.T ) {
1416 fn := func () { _ , _ = scan [int ](nil , nil ) }
15- assert .Panics [E ](t , fn , "queries: T must be a struct" )
17+ assert .Panics [E ](t , fn , "queries: no columns specified" )
18+ })
19+
20+ t .Run ("unsupported T" , func (t * testing.T ) {
21+ columns := []string {"foo" , "bar" }
22+
23+ fn := func () { _ , _ = scan [complex64 ](nil , columns ) }
24+ assert .Panics [E ](t , fn , "queries: unsupported T complex64" )
25+ })
26+
27+ t .Run ("non-struct T with len(columns) > 1" , func (t * testing.T ) {
28+ columns := []string {"foo" , "bar" }
29+
30+ fn := func () { _ , _ = scan [int ](nil , columns ) }
31+ assert .Panics [E ](t , fn , "queries: T must be a struct if len(columns) > 1" )
1632 })
1733
1834 t .Run ("empty tag" , func (t * testing.T ) {
35+ columns := []string {"foo" , "bar" }
36+
1937 type row struct {
20- Foo int `sql:""`
38+ Foo int `sql:"foo"`
39+ Bar string `sql:""`
2140 }
22- fn := func () { _ , _ = scan [row ](nil , nil ) }
23- assert .Panics [E ](t , fn , "queries: field Foo has an empty `sql` tag" )
41+ fn := func () { _ , _ = scan [row ](nil , columns ) }
42+ assert .Panics [E ](t , fn , "queries: field Bar has an empty `sql` tag" )
2443 })
2544
2645 t .Run ("missing field" , func (t * testing.T ) {
46+ columns := []string {"foo" , "bar" }
47+
2748 type row struct {
2849 Foo int `sql:"foo"`
2950 Bar string
3051 }
31- fn := func () { _ , _ = scan [row ](nil , [] string { "foo" , "bar" } ) }
52+ fn := func () { _ , _ = scan [row ](nil , columns ) }
3253 assert .Panics [E ](t , fn , `queries: no field for column "bar"` )
3354 })
3455
3556 t .Run ("scan error" , func (t * testing.T ) {
36- columns := []string {"foo" }
57+ columns := []string {"foo" , "bar" }
3758 s := mockScanner {err : errors .New ("an error" )}
3859
3960 type row struct {
40- Foo int `sql:"foo"`
61+ Foo int `sql:"foo"`
62+ Bar string `sql:"bar"`
4163 }
4264 _ , err := scan [row ](& s , columns )
4365 assert .IsErr [E ](t , err , s .err )
4466 })
4567
46- t .Run ("ok " , func (t * testing.T ) {
68+ t .Run ("struct T " , func (t * testing.T ) {
4769 columns := []string {"foo" , "bar" }
48- s := mockScanner {values : []any {1 , "A " }}
70+ s := mockScanner {values : []any {1 , "test " }}
4971
5072 type row struct {
5173 Foo int `sql:"foo"`
5274 Bar string `sql:"bar"`
5375 unexported bool
5476 }
55- r , err := scan [row ](& s , columns )
77+ v , err := scan [row ](& s , columns )
78+ assert .NoErr [F ](t , err )
79+ assert .Equal [E ](t , v .Foo , 1 )
80+ assert .Equal [E ](t , v .Bar , "test" )
81+ assert .Equal [E ](t , v .unexported , false )
82+ })
83+
84+ t .Run ("struct T with len(columns) == 1" , func (t * testing.T ) {
85+ columns := []string {"foo" }
86+ s := mockScanner {values : []any {1 }}
87+
88+ type row struct {
89+ Foo int `sql:"foo"`
90+ }
91+ v , err := scan [row ](& s , columns )
92+ assert .NoErr [F ](t , err )
93+ assert .Equal [E ](t , v .Foo , 1 )
94+ })
95+
96+ t .Run ("non-struct T with len(columns) == 1" , func (t * testing.T ) {
97+ columns := []string {"foo" }
98+
99+ tests := []struct {
100+ scan func (scanner ) (any , error )
101+ value any
102+ }{
103+ {func (s scanner ) (any , error ) { return scan [bool ](s , columns ) }, true },
104+ {func (s scanner ) (any , error ) { return scan [int ](s , columns ) }, int (- 1 )},
105+ {func (s scanner ) (any , error ) { return scan [int8 ](s , columns ) }, int8 (- 8 )},
106+ {func (s scanner ) (any , error ) { return scan [int16 ](s , columns ) }, int16 (- 16 )},
107+ {func (s scanner ) (any , error ) { return scan [int32 ](s , columns ) }, int32 (- 32 )},
108+ {func (s scanner ) (any , error ) { return scan [int64 ](s , columns ) }, int64 (- 64 )},
109+ {func (s scanner ) (any , error ) { return scan [uint ](s , columns ) }, uint (1 )},
110+ {func (s scanner ) (any , error ) { return scan [uint8 ](s , columns ) }, uint8 (8 )},
111+ {func (s scanner ) (any , error ) { return scan [uint16 ](s , columns ) }, uint16 (16 )},
112+ {func (s scanner ) (any , error ) { return scan [uint32 ](s , columns ) }, uint32 (32 )},
113+ {func (s scanner ) (any , error ) { return scan [uint64 ](s , columns ) }, uint64 (64 )},
114+ {func (s scanner ) (any , error ) { return scan [float32 ](s , columns ) }, float32 (0.32 )},
115+ {func (s scanner ) (any , error ) { return scan [float64 ](s , columns ) }, float64 (0.64 )},
116+ {func (s scanner ) (any , error ) { return scan [string ](s , columns ) }, "test" },
117+ {func (s scanner ) (any , error ) { return scan [time.Time ](s , columns ) }, time .Now ()},
118+ }
119+ for _ , tt := range tests {
120+ s := mockScanner {values : []any {tt .value }}
121+ v , err := tt .scan (& s )
122+ assert .NoErr [F ](t , err )
123+ assert .Equal [E ](t , v , tt .value )
124+ }
125+
126+ // sql.Scanner implementation:
127+ s := mockScanner {values : []any {"test" }}
128+ v , err := scan [sql.Null [string ]](& s , columns )
56129 assert .NoErr [F ](t , err )
57- assert .Equal [E ](t , r .Foo , 1 )
58- assert .Equal [E ](t , r .Bar , "A" )
59- assert .Equal [E ](t , r .unexported , false )
130+ assert .Equal [E ](t , v , sql.Null [string ]{V : "test" , Valid : true })
60131 })
61132}
62133
@@ -70,8 +141,14 @@ func (s *mockScanner) Scan(dst ...any) error {
70141 return s .err
71142 }
72143 for i := range dst {
73- v := reflect .ValueOf (s .values [i ])
74- reflect .ValueOf (dst [i ]).Elem ().Set (v )
144+ if sc , ok := dst [i ].(sql.Scanner ); ok {
145+ if err := sc .Scan (s .values [i ]); err != nil {
146+ return err
147+ }
148+ } else {
149+ v := reflect .ValueOf (s .values [i ])
150+ reflect .ValueOf (dst [i ]).Elem ().Set (v )
151+ }
75152 }
76153 return nil
77154}
0 commit comments