summaryrefslogtreecommitdiff
path: root/vendor/github.com/jackc/pgx/v5/rows.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/jackc/pgx/v5/rows.go')
-rw-r--r--vendor/github.com/jackc/pgx/v5/rows.go856
1 files changed, 0 insertions, 856 deletions
diff --git a/vendor/github.com/jackc/pgx/v5/rows.go b/vendor/github.com/jackc/pgx/v5/rows.go
deleted file mode 100644
index f23625d4c..000000000
--- a/vendor/github.com/jackc/pgx/v5/rows.go
+++ /dev/null
@@ -1,856 +0,0 @@
-package pgx
-
-import (
- "context"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "sync"
- "time"
-
- "github.com/jackc/pgx/v5/pgconn"
- "github.com/jackc/pgx/v5/pgtype"
-)
-
-// Rows is the result set returned from *Conn.Query. Rows must be closed before
-// the *Conn can be used again. Rows are closed by explicitly calling Close(),
-// calling Next() until it returns false, or when a fatal error occurs.
-//
-// Once a Rows is closed the only methods that may be called are Close(), Err(),
-// and CommandTag().
-//
-// Rows is an interface instead of a struct to allow tests to mock Query. However,
-// adding a method to an interface is technically a breaking change. Because of this
-// the Rows interface is partially excluded from semantic version requirements.
-// Methods will not be removed or changed, but new methods may be added.
-type Rows interface {
- // Close closes the rows, making the connection ready for use again. It is safe
- // to call Close after rows is already closed.
- Close()
-
- // Err returns any error that occurred while reading. Err must only be called after the Rows is closed (either by
- // calling Close or by Next returning false). If it is called early it may return nil even if there was an error
- // executing the query.
- Err() error
-
- // CommandTag returns the command tag from this query. It is only available after Rows is closed.
- CommandTag() pgconn.CommandTag
-
- // FieldDescriptions returns the field descriptions of the columns. It may return nil. In particular this can occur
- // when there was an error executing the query.
- FieldDescriptions() []pgconn.FieldDescription
-
- // Next prepares the next row for reading. It returns true if there is another
- // row and false if no more rows are available or a fatal error has occurred.
- // It automatically closes rows when all rows are read.
- //
- // Callers should check rows.Err() after rows.Next() returns false to detect
- // whether result-set reading ended prematurely due to an error. See
- // Conn.Query for details.
- //
- // For simpler error handling, consider using the higher-level pgx v5
- // CollectRows() and ForEachRow() helpers instead.
- Next() bool
-
- // Scan reads the values from the current row into dest values positionally.
- // dest can include pointers to core types, values implementing the Scanner
- // interface, and nil. nil will skip the value entirely. It is an error to
- // call Scan without first calling Next() and checking that it returned true.
- Scan(dest ...any) error
-
- // Values returns the decoded row values. As with Scan(), it is an error to
- // call Values without first calling Next() and checking that it returned
- // true.
- Values() ([]any, error)
-
- // RawValues returns the unparsed bytes of the row values. The returned data is only valid until the next Next
- // call or the Rows is closed.
- RawValues() [][]byte
-
- // Conn returns the underlying *Conn on which the query was executed. This may return nil if Rows did not come from a
- // *Conn (e.g. if it was created by RowsFromResultReader)
- Conn() *Conn
-}
-
-// Row is a convenience wrapper over Rows that is returned by QueryRow.
-//
-// Row is an interface instead of a struct to allow tests to mock QueryRow. However,
-// adding a method to an interface is technically a breaking change. Because of this
-// the Row interface is partially excluded from semantic version requirements.
-// Methods will not be removed or changed, but new methods may be added.
-type Row interface {
- // Scan works the same as Rows. with the following exceptions. If no
- // rows were found it returns ErrNoRows. If multiple rows are returned it
- // ignores all but the first.
- Scan(dest ...any) error
-}
-
-// RowScanner scans an entire row at a time into the RowScanner.
-type RowScanner interface {
- // ScanRows scans the row.
- ScanRow(rows Rows) error
-}
-
-// connRow implements the Row interface for Conn.QueryRow.
-type connRow baseRows
-
-func (r *connRow) Scan(dest ...any) (err error) {
- rows := (*baseRows)(r)
-
- if rows.Err() != nil {
- return rows.Err()
- }
-
- for _, d := range dest {
- if _, ok := d.(*pgtype.DriverBytes); ok {
- rows.Close()
- return fmt.Errorf("cannot scan into *pgtype.DriverBytes from QueryRow")
- }
- }
-
- if !rows.Next() {
- if rows.Err() == nil {
- return ErrNoRows
- }
- return rows.Err()
- }
-
- rows.Scan(dest...)
- rows.Close()
- return rows.Err()
-}
-
-// baseRows implements the Rows interface for Conn.Query.
-type baseRows struct {
- typeMap *pgtype.Map
- resultReader *pgconn.ResultReader
-
- values [][]byte
-
- commandTag pgconn.CommandTag
- err error
- closed bool
-
- scanPlans []pgtype.ScanPlan
- scanTypes []reflect.Type
-
- conn *Conn
- multiResultReader *pgconn.MultiResultReader
-
- queryTracer QueryTracer
- batchTracer BatchTracer
- ctx context.Context
- startTime time.Time
- sql string
- args []any
- rowCount int
-}
-
-func (rows *baseRows) FieldDescriptions() []pgconn.FieldDescription {
- return rows.resultReader.FieldDescriptions()
-}
-
-func (rows *baseRows) Close() {
- if rows.closed {
- return
- }
-
- rows.closed = true
-
- if rows.resultReader != nil {
- var closeErr error
- rows.commandTag, closeErr = rows.resultReader.Close()
- if rows.err == nil {
- rows.err = closeErr
- }
- }
-
- if rows.multiResultReader != nil {
- closeErr := rows.multiResultReader.Close()
- if rows.err == nil {
- rows.err = closeErr
- }
- }
-
- if rows.err != nil && rows.conn != nil && rows.sql != "" {
- if sc := rows.conn.statementCache; sc != nil {
- sc.Invalidate(rows.sql)
- }
-
- if sc := rows.conn.descriptionCache; sc != nil {
- sc.Invalidate(rows.sql)
- }
- }
-
- if rows.batchTracer != nil {
- rows.batchTracer.TraceBatchQuery(rows.ctx, rows.conn, TraceBatchQueryData{SQL: rows.sql, Args: rows.args, CommandTag: rows.commandTag, Err: rows.err})
- } else if rows.queryTracer != nil {
- rows.queryTracer.TraceQueryEnd(rows.ctx, rows.conn, TraceQueryEndData{rows.commandTag, rows.err})
- }
-}
-
-func (rows *baseRows) CommandTag() pgconn.CommandTag {
- return rows.commandTag
-}
-
-func (rows *baseRows) Err() error {
- return rows.err
-}
-
-// fatal signals an error occurred after the query was sent to the server. It
-// closes the rows automatically.
-func (rows *baseRows) fatal(err error) {
- if rows.err != nil {
- return
- }
-
- rows.err = err
- rows.Close()
-}
-
-func (rows *baseRows) Next() bool {
- if rows.closed {
- return false
- }
-
- if rows.resultReader.NextRow() {
- rows.rowCount++
- rows.values = rows.resultReader.Values()
- return true
- } else {
- rows.Close()
- return false
- }
-}
-
-func (rows *baseRows) Scan(dest ...any) error {
- m := rows.typeMap
- fieldDescriptions := rows.FieldDescriptions()
- values := rows.values
-
- if len(fieldDescriptions) != len(values) {
- err := fmt.Errorf("number of field descriptions must equal number of values, got %d and %d", len(fieldDescriptions), len(values))
- rows.fatal(err)
- return err
- }
-
- if len(dest) == 1 {
- if rc, ok := dest[0].(RowScanner); ok {
- err := rc.ScanRow(rows)
- if err != nil {
- rows.fatal(err)
- }
- return err
- }
- }
-
- if len(fieldDescriptions) != len(dest) {
- err := fmt.Errorf("number of field descriptions must equal number of destinations, got %d and %d", len(fieldDescriptions), len(dest))
- rows.fatal(err)
- return err
- }
-
- if rows.scanPlans == nil {
- rows.scanPlans = make([]pgtype.ScanPlan, len(values))
- rows.scanTypes = make([]reflect.Type, len(values))
- for i := range dest {
- rows.scanPlans[i] = m.PlanScan(fieldDescriptions[i].DataTypeOID, fieldDescriptions[i].Format, dest[i])
- rows.scanTypes[i] = reflect.TypeOf(dest[i])
- }
- }
-
- for i, dst := range dest {
- if dst == nil {
- continue
- }
-
- if rows.scanTypes[i] != reflect.TypeOf(dst) {
- rows.scanPlans[i] = m.PlanScan(fieldDescriptions[i].DataTypeOID, fieldDescriptions[i].Format, dest[i])
- rows.scanTypes[i] = reflect.TypeOf(dest[i])
- }
-
- err := rows.scanPlans[i].Scan(values[i], dst)
- if err != nil {
- err = ScanArgError{ColumnIndex: i, Err: err}
- rows.fatal(err)
- return err
- }
- }
-
- return nil
-}
-
-func (rows *baseRows) Values() ([]any, error) {
- if rows.closed {
- return nil, errors.New("rows is closed")
- }
-
- values := make([]any, 0, len(rows.FieldDescriptions()))
-
- for i := range rows.FieldDescriptions() {
- buf := rows.values[i]
- fd := &rows.FieldDescriptions()[i]
-
- if buf == nil {
- values = append(values, nil)
- continue
- }
-
- if dt, ok := rows.typeMap.TypeForOID(fd.DataTypeOID); ok {
- value, err := dt.Codec.DecodeValue(rows.typeMap, fd.DataTypeOID, fd.Format, buf)
- if err != nil {
- rows.fatal(err)
- }
- values = append(values, value)
- } else {
- switch fd.Format {
- case TextFormatCode:
- values = append(values, string(buf))
- case BinaryFormatCode:
- newBuf := make([]byte, len(buf))
- copy(newBuf, buf)
- values = append(values, newBuf)
- default:
- rows.fatal(errors.New("unknown format code"))
- }
- }
-
- if rows.Err() != nil {
- return nil, rows.Err()
- }
- }
-
- return values, rows.Err()
-}
-
-func (rows *baseRows) RawValues() [][]byte {
- return rows.values
-}
-
-func (rows *baseRows) Conn() *Conn {
- return rows.conn
-}
-
-type ScanArgError struct {
- ColumnIndex int
- Err error
-}
-
-func (e ScanArgError) Error() string {
- return fmt.Sprintf("can't scan into dest[%d]: %v", e.ColumnIndex, e.Err)
-}
-
-func (e ScanArgError) Unwrap() error {
- return e.Err
-}
-
-// ScanRow decodes raw row data into dest. It can be used to scan rows read from the lower level pgconn interface.
-//
-// typeMap - OID to Go type mapping.
-// fieldDescriptions - OID and format of values
-// values - the raw data as returned from the PostgreSQL server
-// dest - the destination that values will be decoded into
-func ScanRow(typeMap *pgtype.Map, fieldDescriptions []pgconn.FieldDescription, values [][]byte, dest ...any) error {
- if len(fieldDescriptions) != len(values) {
- return fmt.Errorf("number of field descriptions must equal number of values, got %d and %d", len(fieldDescriptions), len(values))
- }
- if len(fieldDescriptions) != len(dest) {
- return fmt.Errorf("number of field descriptions must equal number of destinations, got %d and %d", len(fieldDescriptions), len(dest))
- }
-
- for i, d := range dest {
- if d == nil {
- continue
- }
-
- err := typeMap.Scan(fieldDescriptions[i].DataTypeOID, fieldDescriptions[i].Format, values[i], d)
- if err != nil {
- return ScanArgError{ColumnIndex: i, Err: err}
- }
- }
-
- return nil
-}
-
-// RowsFromResultReader returns a Rows that will read from values resultReader and decode with typeMap. It can be used
-// to read from the lower level pgconn interface.
-func RowsFromResultReader(typeMap *pgtype.Map, resultReader *pgconn.ResultReader) Rows {
- return &baseRows{
- typeMap: typeMap,
- resultReader: resultReader,
- }
-}
-
-// ForEachRow iterates through rows. For each row it scans into the elements of scans and calls fn. If any row
-// fails to scan or fn returns an error the query will be aborted and the error will be returned. Rows will be closed
-// when ForEachRow returns.
-func ForEachRow(rows Rows, scans []any, fn func() error) (pgconn.CommandTag, error) {
- defer rows.Close()
-
- for rows.Next() {
- err := rows.Scan(scans...)
- if err != nil {
- return pgconn.CommandTag{}, err
- }
-
- err = fn()
- if err != nil {
- return pgconn.CommandTag{}, err
- }
- }
-
- if err := rows.Err(); err != nil {
- return pgconn.CommandTag{}, err
- }
-
- return rows.CommandTag(), nil
-}
-
-// CollectableRow is the subset of Rows methods that a RowToFunc is allowed to call.
-type CollectableRow interface {
- FieldDescriptions() []pgconn.FieldDescription
- Scan(dest ...any) error
- Values() ([]any, error)
- RawValues() [][]byte
-}
-
-// RowToFunc is a function that scans or otherwise converts row to a T.
-type RowToFunc[T any] func(row CollectableRow) (T, error)
-
-// AppendRows iterates through rows, calling fn for each row, and appending the results into a slice of T.
-//
-// This function closes the rows automatically on return.
-func AppendRows[T any, S ~[]T](slice S, rows Rows, fn RowToFunc[T]) (S, error) {
- defer rows.Close()
-
- for rows.Next() {
- value, err := fn(rows)
- if err != nil {
- return nil, err
- }
- slice = append(slice, value)
- }
-
- if err := rows.Err(); err != nil {
- return nil, err
- }
-
- return slice, nil
-}
-
-// CollectRows iterates through rows, calling fn for each row, and collecting the results into a slice of T.
-//
-// This function closes the rows automatically on return.
-func CollectRows[T any](rows Rows, fn RowToFunc[T]) ([]T, error) {
- return AppendRows([]T{}, rows, fn)
-}
-
-// CollectOneRow calls fn for the first row in rows and returns the result. If no rows are found returns an error where errors.Is(ErrNoRows) is true.
-// CollectOneRow is to CollectRows as QueryRow is to Query.
-//
-// This function closes the rows automatically on return.
-func CollectOneRow[T any](rows Rows, fn RowToFunc[T]) (T, error) {
- defer rows.Close()
-
- var value T
- var err error
-
- if !rows.Next() {
- if err = rows.Err(); err != nil {
- return value, err
- }
- return value, ErrNoRows
- }
-
- value, err = fn(rows)
- if err != nil {
- return value, err
- }
-
- rows.Close()
- return value, rows.Err()
-}
-
-// CollectExactlyOneRow calls fn for the first row in rows and returns the result.
-// - If no rows are found returns an error where errors.Is(ErrNoRows) is true.
-// - If more than 1 row is found returns an error where errors.Is(ErrTooManyRows) is true.
-//
-// This function closes the rows automatically on return.
-func CollectExactlyOneRow[T any](rows Rows, fn RowToFunc[T]) (T, error) {
- defer rows.Close()
-
- var (
- err error
- value T
- )
-
- if !rows.Next() {
- if err = rows.Err(); err != nil {
- return value, err
- }
-
- return value, ErrNoRows
- }
-
- value, err = fn(rows)
- if err != nil {
- return value, err
- }
-
- if rows.Next() {
- var zero T
-
- return zero, ErrTooManyRows
- }
-
- return value, rows.Err()
-}
-
-// RowTo returns a T scanned from row.
-func RowTo[T any](row CollectableRow) (T, error) {
- var value T
- err := row.Scan(&value)
- return value, err
-}
-
-// RowTo returns a the address of a T scanned from row.
-func RowToAddrOf[T any](row CollectableRow) (*T, error) {
- var value T
- err := row.Scan(&value)
- return &value, err
-}
-
-// RowToMap returns a map scanned from row.
-func RowToMap(row CollectableRow) (map[string]any, error) {
- var value map[string]any
- err := row.Scan((*mapRowScanner)(&value))
- return value, err
-}
-
-type mapRowScanner map[string]any
-
-func (rs *mapRowScanner) ScanRow(rows Rows) error {
- values, err := rows.Values()
- if err != nil {
- return err
- }
-
- *rs = make(mapRowScanner, len(values))
-
- for i := range values {
- (*rs)[string(rows.FieldDescriptions()[i].Name)] = values[i]
- }
-
- return nil
-}
-
-// RowToStructByPos returns a T scanned from row. T must be a struct. T must have the same number a public fields as row
-// has fields. The row and T fields will be matched by position. If the "db" struct tag is "-" then the field will be
-// ignored.
-func RowToStructByPos[T any](row CollectableRow) (T, error) {
- var value T
- err := (&positionalStructRowScanner{ptrToStruct: &value}).ScanRow(row)
- return value, err
-}
-
-// RowToAddrOfStructByPos returns the address of a T scanned from row. T must be a struct. T must have the same number a
-// public fields as row has fields. The row and T fields will be matched by position. If the "db" struct tag is "-" then
-// the field will be ignored.
-func RowToAddrOfStructByPos[T any](row CollectableRow) (*T, error) {
- var value T
- err := (&positionalStructRowScanner{ptrToStruct: &value}).ScanRow(row)
- return &value, err
-}
-
-type positionalStructRowScanner struct {
- ptrToStruct any
-}
-
-func (rs *positionalStructRowScanner) ScanRow(rows CollectableRow) error {
- typ := reflect.TypeOf(rs.ptrToStruct).Elem()
- fields := lookupStructFields(typ)
- if len(rows.RawValues()) > len(fields) {
- return fmt.Errorf(
- "got %d values, but dst struct has only %d fields",
- len(rows.RawValues()),
- len(fields),
- )
- }
- scanTargets := setupStructScanTargets(rs.ptrToStruct, fields)
- return rows.Scan(scanTargets...)
-}
-
-// Map from reflect.Type -> []structRowField
-var positionalStructFieldMap sync.Map
-
-func lookupStructFields(t reflect.Type) []structRowField {
- if cached, ok := positionalStructFieldMap.Load(t); ok {
- return cached.([]structRowField)
- }
-
- fieldStack := make([]int, 0, 1)
- fields := computeStructFields(t, make([]structRowField, 0, t.NumField()), &fieldStack)
- fieldsIface, _ := positionalStructFieldMap.LoadOrStore(t, fields)
- return fieldsIface.([]structRowField)
-}
-
-func computeStructFields(
- t reflect.Type,
- fields []structRowField,
- fieldStack *[]int,
-) []structRowField {
- tail := len(*fieldStack)
- *fieldStack = append(*fieldStack, 0)
- for i := 0; i < t.NumField(); i++ {
- sf := t.Field(i)
- (*fieldStack)[tail] = i
- // Handle anonymous struct embedding, but do not try to handle embedded pointers.
- if sf.Anonymous && sf.Type.Kind() == reflect.Struct {
- fields = computeStructFields(sf.Type, fields, fieldStack)
- } else if sf.PkgPath == "" {
- dbTag, _ := sf.Tag.Lookup(structTagKey)
- if dbTag == "-" {
- // Field is ignored, skip it.
- continue
- }
- fields = append(fields, structRowField{
- path: append([]int(nil), *fieldStack...),
- })
- }
- }
- *fieldStack = (*fieldStack)[:tail]
- return fields
-}
-
-// RowToStructByName returns a T scanned from row. T must be a struct. T must have the same number of named public
-// fields as row has fields. The row and T fields will be matched by name. The match is case-insensitive. The database
-// column name can be overridden with a "db" struct tag. If the "db" struct tag is "-" then the field will be ignored.
-func RowToStructByName[T any](row CollectableRow) (T, error) {
- var value T
- err := (&namedStructRowScanner{ptrToStruct: &value}).ScanRow(row)
- return value, err
-}
-
-// RowToAddrOfStructByName returns the address of a T scanned from row. T must be a struct. T must have the same number
-// of named public fields as row has fields. The row and T fields will be matched by name. The match is
-// case-insensitive. The database column name can be overridden with a "db" struct tag. If the "db" struct tag is "-"
-// then the field will be ignored.
-func RowToAddrOfStructByName[T any](row CollectableRow) (*T, error) {
- var value T
- err := (&namedStructRowScanner{ptrToStruct: &value}).ScanRow(row)
- return &value, err
-}
-
-// RowToStructByNameLax returns a T scanned from row. T must be a struct. T must have greater than or equal number of named public
-// fields as row has fields. The row and T fields will be matched by name. The match is case-insensitive. The database
-// column name can be overridden with a "db" struct tag. If the "db" struct tag is "-" then the field will be ignored.
-func RowToStructByNameLax[T any](row CollectableRow) (T, error) {
- var value T
- err := (&namedStructRowScanner{ptrToStruct: &value, lax: true}).ScanRow(row)
- return value, err
-}
-
-// RowToAddrOfStructByNameLax returns the address of a T scanned from row. T must be a struct. T must have greater than or
-// equal number of named public fields as row has fields. The row and T fields will be matched by name. The match is
-// case-insensitive. The database column name can be overridden with a "db" struct tag. If the "db" struct tag is "-"
-// then the field will be ignored.
-func RowToAddrOfStructByNameLax[T any](row CollectableRow) (*T, error) {
- var value T
- err := (&namedStructRowScanner{ptrToStruct: &value, lax: true}).ScanRow(row)
- return &value, err
-}
-
-type namedStructRowScanner struct {
- ptrToStruct any
- lax bool
-}
-
-func (rs *namedStructRowScanner) ScanRow(rows CollectableRow) error {
- typ := reflect.TypeOf(rs.ptrToStruct).Elem()
- fldDescs := rows.FieldDescriptions()
- namedStructFields, err := lookupNamedStructFields(typ, fldDescs)
- if err != nil {
- return err
- }
- if !rs.lax && namedStructFields.missingField != "" {
- return fmt.Errorf("cannot find field %s in returned row", namedStructFields.missingField)
- }
- fields := namedStructFields.fields
- scanTargets := setupStructScanTargets(rs.ptrToStruct, fields)
- return rows.Scan(scanTargets...)
-}
-
-// Map from namedStructFieldMap -> *namedStructFields
-var namedStructFieldMap sync.Map
-
-type namedStructFieldsKey struct {
- t reflect.Type
- colNames string
-}
-
-type namedStructFields struct {
- fields []structRowField
- // missingField is the first field from the struct without a corresponding row field.
- // This is used to construct the correct error message for non-lax queries.
- missingField string
-}
-
-func lookupNamedStructFields(
- t reflect.Type,
- fldDescs []pgconn.FieldDescription,
-) (*namedStructFields, error) {
- key := namedStructFieldsKey{
- t: t,
- colNames: joinFieldNames(fldDescs),
- }
- if cached, ok := namedStructFieldMap.Load(key); ok {
- return cached.(*namedStructFields), nil
- }
-
- // We could probably do two-levels of caching, where we compute the key -> fields mapping
- // for a type only once, cache it by type, then use that to compute the column -> fields
- // mapping for a given set of columns.
- fieldStack := make([]int, 0, 1)
- fields, missingField := computeNamedStructFields(
- fldDescs,
- t,
- make([]structRowField, len(fldDescs)),
- &fieldStack,
- )
- for i, f := range fields {
- if f.path == nil {
- return nil, fmt.Errorf(
- "struct doesn't have corresponding row field %s",
- fldDescs[i].Name,
- )
- }
- }
-
- fieldsIface, _ := namedStructFieldMap.LoadOrStore(
- key,
- &namedStructFields{fields: fields, missingField: missingField},
- )
- return fieldsIface.(*namedStructFields), nil
-}
-
-func joinFieldNames(fldDescs []pgconn.FieldDescription) string {
- switch len(fldDescs) {
- case 0:
- return ""
- case 1:
- return fldDescs[0].Name
- }
-
- totalSize := len(fldDescs) - 1 // Space for separator bytes.
- for _, d := range fldDescs {
- totalSize += len(d.Name)
- }
- var b strings.Builder
- b.Grow(totalSize)
- b.WriteString(fldDescs[0].Name)
- for _, d := range fldDescs[1:] {
- b.WriteByte(0) // Join with NUL byte as it's (presumably) not a valid column character.
- b.WriteString(d.Name)
- }
- return b.String()
-}
-
-func computeNamedStructFields(
- fldDescs []pgconn.FieldDescription,
- t reflect.Type,
- fields []structRowField,
- fieldStack *[]int,
-) ([]structRowField, string) {
- var missingField string
- tail := len(*fieldStack)
- *fieldStack = append(*fieldStack, 0)
- for i := 0; i < t.NumField(); i++ {
- sf := t.Field(i)
- (*fieldStack)[tail] = i
- if sf.PkgPath != "" && !sf.Anonymous {
- // Field is unexported, skip it.
- continue
- }
- // Handle anonymous struct embedding, but do not try to handle embedded pointers.
- if sf.Anonymous && sf.Type.Kind() == reflect.Struct {
- var missingSubField string
- fields, missingSubField = computeNamedStructFields(
- fldDescs,
- sf.Type,
- fields,
- fieldStack,
- )
- if missingField == "" {
- missingField = missingSubField
- }
- } else {
- dbTag, dbTagPresent := sf.Tag.Lookup(structTagKey)
- if dbTagPresent {
- dbTag, _, _ = strings.Cut(dbTag, ",")
- }
- if dbTag == "-" {
- // Field is ignored, skip it.
- continue
- }
- colName := dbTag
- if !dbTagPresent {
- colName = sf.Name
- }
- fpos := fieldPosByName(fldDescs, colName, !dbTagPresent)
- if fpos == -1 {
- if missingField == "" {
- missingField = colName
- }
- continue
- }
- fields[fpos] = structRowField{
- path: append([]int(nil), *fieldStack...),
- }
- }
- }
- *fieldStack = (*fieldStack)[:tail]
-
- return fields, missingField
-}
-
-const structTagKey = "db"
-
-func fieldPosByName(fldDescs []pgconn.FieldDescription, field string, normalize bool) (i int) {
- i = -1
-
- if normalize {
- field = strings.ReplaceAll(field, "_", "")
- }
- for i, desc := range fldDescs {
- if normalize {
- if strings.EqualFold(strings.ReplaceAll(desc.Name, "_", ""), field) {
- return i
- }
- } else {
- if desc.Name == field {
- return i
- }
- }
- }
- return
-}
-
-// structRowField describes a field of a struct.
-//
-// TODO: It would be a bit more efficient to track the path using the pointer
-// offset within the (outermost) struct and use unsafe.Pointer arithmetic to
-// construct references when scanning rows. However, it's not clear it's worth
-// using unsafe for this.
-type structRowField struct {
- path []int
-}
-
-func setupStructScanTargets(receiver any, fields []structRowField) []any {
- scanTargets := make([]any, len(fields))
- v := reflect.ValueOf(receiver).Elem()
- for i, f := range fields {
- scanTargets[i] = v.FieldByIndex(f.path).Addr().Interface()
- }
- return scanTargets
-}