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  }