github.com/supabase/cli@v1.168.1/internal/utils/pgxv5/rows.go (about)

     1  // Backported from pgx/v5: https://github.com/jackc/pgx/blob/v5.3.1/rows.go#L408
     2  package pgxv5
     3  
     4  import (
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/go-errors/errors"
     9  	"github.com/jackc/pgproto3/v2"
    10  	"github.com/jackc/pgx/v4"
    11  )
    12  
    13  func CollectStrings(rows pgx.Rows) ([]string, error) {
    14  	result := []string{}
    15  	for rows.Next() {
    16  		var version string
    17  		if err := rows.Scan(&version); err != nil {
    18  			return nil, errors.Errorf("failed to scan rows: %w", err)
    19  		}
    20  		result = append(result, version)
    21  	}
    22  	if err := rows.Err(); err != nil {
    23  		return nil, errors.Errorf("failed to parse rows: %w", err)
    24  	}
    25  	return result, nil
    26  }
    27  
    28  // CollectRows iterates through rows, calling fn for each row, and collecting the results into a slice of T.
    29  func CollectRows[T any](rows pgx.Rows) ([]T, error) {
    30  	defer rows.Close()
    31  
    32  	slice := []T{}
    33  
    34  	for rows.Next() {
    35  		var value T
    36  		if err := ScanRowToStruct(rows, &value); err != nil {
    37  			return nil, err
    38  		}
    39  		slice = append(slice, value)
    40  	}
    41  
    42  	if err := rows.Err(); err != nil {
    43  		return nil, errors.Errorf("failed to collect rows: %w", err)
    44  	}
    45  
    46  	return slice, nil
    47  }
    48  
    49  func ScanRowToStruct(rows pgx.Rows, dst any) error {
    50  	dstValue := reflect.ValueOf(dst)
    51  	if dstValue.Kind() != reflect.Ptr {
    52  		return errors.Errorf("dst not a pointer")
    53  	}
    54  
    55  	dstElemValue := dstValue.Elem()
    56  	scanTargets, err := appendScanTargets(dstElemValue, nil, rows.FieldDescriptions())
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	for i, t := range scanTargets {
    62  		if t == nil {
    63  			return errors.Errorf("struct doesn't have corresponding row field %s", rows.FieldDescriptions()[i].Name)
    64  		}
    65  	}
    66  
    67  	if err := rows.Scan(scanTargets...); err != nil {
    68  		return errors.Errorf("failed to scan targets: %w", err)
    69  	}
    70  	return nil
    71  }
    72  
    73  const structTagKey = "db"
    74  
    75  func fieldPosByName(fldDescs []pgproto3.FieldDescription, field string) (i int) {
    76  	i = -1
    77  	for i, desc := range fldDescs {
    78  		if strings.EqualFold(string(desc.Name), field) {
    79  			return i
    80  		}
    81  	}
    82  	return
    83  }
    84  
    85  func appendScanTargets(dstElemValue reflect.Value, scanTargets []any, fldDescs []pgproto3.FieldDescription) ([]any, error) {
    86  	var err error
    87  	dstElemType := dstElemValue.Type()
    88  
    89  	if scanTargets == nil {
    90  		scanTargets = make([]any, len(fldDescs))
    91  	}
    92  
    93  	for i := 0; i < dstElemType.NumField(); i++ {
    94  		sf := dstElemType.Field(i)
    95  		if sf.PkgPath != "" && !sf.Anonymous {
    96  			// Field is unexported, skip it.
    97  			continue
    98  		}
    99  		// Handle anoymous struct embedding, but do not try to handle embedded pointers.
   100  		if sf.Anonymous && sf.Type.Kind() == reflect.Struct {
   101  			scanTargets, err = appendScanTargets(dstElemValue.Field(i), scanTargets, fldDescs)
   102  			if err != nil {
   103  				return nil, err
   104  			}
   105  		} else {
   106  			dbTag, dbTagPresent := sf.Tag.Lookup(structTagKey)
   107  			if dbTagPresent {
   108  				dbTag = strings.Split(dbTag, ",")[0]
   109  			}
   110  			if dbTag == "-" {
   111  				// Field is ignored, skip it.
   112  				continue
   113  			}
   114  			colName := dbTag
   115  			if !dbTagPresent {
   116  				colName = sf.Name
   117  			}
   118  			fpos := fieldPosByName(fldDescs, colName)
   119  			if fpos == -1 || fpos >= len(scanTargets) {
   120  				return nil, errors.Errorf("cannot find field %s in returned row", colName)
   121  			}
   122  			scanTargets[fpos] = dstElemValue.Field(i).Addr().Interface()
   123  		}
   124  	}
   125  
   126  	return scanTargets, err
   127  }