github.com/mitranim/gg@v0.1.17/gsql/gsql_scan.go (about) 1 package gsql 2 3 import ( 4 "database/sql" 5 r "reflect" 6 7 "github.com/mitranim/gg" 8 ) 9 10 // Returned by `ScanVal` and `ScanAny` when there are too many rows. 11 const ErrMultipleRows gg.ErrStr = `expected one row, got multiple` 12 13 /* 14 Takes `Rows` and decodes them into a slice of the given type, using `ScanNext` 15 for each row. Output type must be either scalar or struct. Always closes the 16 rows. 17 */ 18 func ScanVals[Row any, Src Rows](src Src) (out []Row) { 19 defer gg.Close(src) 20 for src.Next() { 21 out = append(out, ScanNext[Row](src)) 22 } 23 gg.ErrOk(src) 24 return 25 } 26 27 /* 28 Takes `Rows` and decodes the first row into a value of the given type, using 29 `ScanNext` once. The rows must consist of EXACTLY one row, otherwise this 30 panics. Output type must be either scalar or struct. Always closes the rows. 31 */ 32 func ScanVal[Row any, Src Rows](src Src) Row { 33 defer gg.Close(src) 34 35 if !src.Next() { 36 panic(gg.AnyErrTraced(sql.ErrNoRows)) 37 } 38 39 out := ScanNext[Row](src) 40 gg.ErrOk(src) 41 42 if src.Next() { 43 panic(gg.AnyErrTraced(ErrMultipleRows)) 44 } 45 return out 46 } 47 48 /* 49 Takes `Rows` and decodes the next row into a value of the given type. Output 50 type must be either scalar or struct. Panics on errors. Must be called only 51 after `Rows.Next`. 52 */ 53 func ScanNext[Row any, Src ColumnerScanner](src Src) Row { 54 if isScalar[Row]() { 55 return scanNextScalar[Row](src) 56 } 57 return scanNextStruct[Row](src) 58 } 59 60 /* 61 Decodes `Rows` into the given dynamically typed output. Counterpart to 62 `ScanVals` and `ScanVal` which are statically typed. Output must be a non-nil 63 pointer to one of the following: 64 65 * Slice of scalars. 66 * Slice of structs. 67 * Single scalar. 68 * Single struct. 69 70 Always closes the rows. If output is not a slice, verifies that there is EXACTLY 71 one row in total, otherwise panics. 72 */ 73 func ScanAny[Src Rows](src Src, out any) { 74 ScanReflect(src, r.ValueOf(out)) 75 } 76 77 // Variant of `ScanAny` that takes a reflect value rather than `any`. 78 func ScanReflect[Src Rows](src Src, out r.Value) { 79 if out.Kind() != r.Pointer { 80 panic(gg.Errf(`scan destination must be a pointer, got %q`, out.Type())) 81 } 82 if out.IsNil() { 83 panic(gg.Errf(`scan destination must be non-nil, got nil %q`, out.Type())) 84 } 85 out = gg.ValueDerefAlloc(out) 86 87 if out.Kind() == r.Slice { 88 scanValsReflect(src, out) 89 } else { 90 scanValReflect(src, out) 91 } 92 }