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 }