an ORM-free SQL experience

begin work on reflection-based scanner

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 1f0a0426 1e5426c6

verified
Changed files
+60
+60
scanner.go
··· 1 + package norm 2 + 3 + import ( 4 + "database/sql" 5 + "iter" 6 + "reflect" 7 + ) 8 + 9 + type Scanner[T any] struct { 10 + rows *sql.Rows 11 + onError func(err error) 12 + } 13 + 14 + type ScannerOpt[T any] func(*Scanner[T]) 15 + 16 + func OnScannerError[T any](errFunc func(error)) ScannerOpt[T] { 17 + return func(s *Scanner[T]) { 18 + s.onError = errFunc 19 + } 20 + } 21 + 22 + func NewScanner[T any](rows *sql.Rows, opts ...ScannerOpt[T]) Scanner[T] { 23 + scanner := Scanner[T]{ 24 + rows: rows, 25 + } 26 + 27 + for _, o := range opts { 28 + o(&scanner) 29 + } 30 + 31 + return scanner 32 + } 33 + 34 + func (s *Scanner[T]) Scan() iter.Seq[T] { 35 + return func(yield func(T) bool) { 36 + defer s.rows.Close() 37 + 38 + for s.rows.Next() { 39 + var data T 40 + elem := reflect.ValueOf(&data).Elem() 41 + numCols := elem.NumField() 42 + columns := make([]any, numCols) 43 + 44 + for i := range numCols { 45 + field := elem.Field(i) 46 + columns[i] = field.Addr().Interface() 47 + } 48 + 49 + if err := s.rows.Scan(columns...); err != nil { 50 + s.onError(err) 51 + return 52 + } 53 + 54 + // Yield the row - if yield returns false, stop iteration 55 + if !yield(data) { 56 + return 57 + } 58 + } 59 + } 60 + }