github.com/jackc/pgx/v5@v5.5.5/pgtype/line.go (about) 1 package pgtype 2 3 import ( 4 "database/sql/driver" 5 "encoding/binary" 6 "fmt" 7 "math" 8 "strconv" 9 "strings" 10 11 "github.com/jackc/pgx/v5/internal/pgio" 12 ) 13 14 type LineScanner interface { 15 ScanLine(v Line) error 16 } 17 18 type LineValuer interface { 19 LineValue() (Line, error) 20 } 21 22 type Line struct { 23 A, B, C float64 24 Valid bool 25 } 26 27 func (line *Line) ScanLine(v Line) error { 28 *line = v 29 return nil 30 } 31 32 func (line Line) LineValue() (Line, error) { 33 return line, nil 34 } 35 36 func (line *Line) Set(src any) error { 37 return fmt.Errorf("cannot convert %v to Line", src) 38 } 39 40 // Scan implements the database/sql Scanner interface. 41 func (line *Line) Scan(src any) error { 42 if src == nil { 43 *line = Line{} 44 return nil 45 } 46 47 switch src := src.(type) { 48 case string: 49 return scanPlanTextAnyToLineScanner{}.Scan([]byte(src), line) 50 } 51 52 return fmt.Errorf("cannot scan %T", src) 53 } 54 55 // Value implements the database/sql/driver Valuer interface. 56 func (line Line) Value() (driver.Value, error) { 57 if !line.Valid { 58 return nil, nil 59 } 60 61 buf, err := LineCodec{}.PlanEncode(nil, 0, TextFormatCode, line).Encode(line, nil) 62 if err != nil { 63 return nil, err 64 } 65 return string(buf), err 66 } 67 68 type LineCodec struct{} 69 70 func (LineCodec) FormatSupported(format int16) bool { 71 return format == TextFormatCode || format == BinaryFormatCode 72 } 73 74 func (LineCodec) PreferredFormat() int16 { 75 return BinaryFormatCode 76 } 77 78 func (LineCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan { 79 if _, ok := value.(LineValuer); !ok { 80 return nil 81 } 82 83 switch format { 84 case BinaryFormatCode: 85 return encodePlanLineCodecBinary{} 86 case TextFormatCode: 87 return encodePlanLineCodecText{} 88 } 89 90 return nil 91 } 92 93 type encodePlanLineCodecBinary struct{} 94 95 func (encodePlanLineCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) { 96 line, err := value.(LineValuer).LineValue() 97 if err != nil { 98 return nil, err 99 } 100 101 if !line.Valid { 102 return nil, nil 103 } 104 105 buf = pgio.AppendUint64(buf, math.Float64bits(line.A)) 106 buf = pgio.AppendUint64(buf, math.Float64bits(line.B)) 107 buf = pgio.AppendUint64(buf, math.Float64bits(line.C)) 108 return buf, nil 109 } 110 111 type encodePlanLineCodecText struct{} 112 113 func (encodePlanLineCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) { 114 line, err := value.(LineValuer).LineValue() 115 if err != nil { 116 return nil, err 117 } 118 119 if !line.Valid { 120 return nil, nil 121 } 122 123 buf = append(buf, fmt.Sprintf(`{%s,%s,%s}`, 124 strconv.FormatFloat(line.A, 'f', -1, 64), 125 strconv.FormatFloat(line.B, 'f', -1, 64), 126 strconv.FormatFloat(line.C, 'f', -1, 64), 127 )...) 128 return buf, nil 129 } 130 131 func (LineCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { 132 133 switch format { 134 case BinaryFormatCode: 135 switch target.(type) { 136 case LineScanner: 137 return scanPlanBinaryLineToLineScanner{} 138 } 139 case TextFormatCode: 140 switch target.(type) { 141 case LineScanner: 142 return scanPlanTextAnyToLineScanner{} 143 } 144 } 145 146 return nil 147 } 148 149 type scanPlanBinaryLineToLineScanner struct{} 150 151 func (scanPlanBinaryLineToLineScanner) Scan(src []byte, dst any) error { 152 scanner := (dst).(LineScanner) 153 154 if src == nil { 155 return scanner.ScanLine(Line{}) 156 } 157 158 if len(src) != 24 { 159 return fmt.Errorf("invalid length for line: %v", len(src)) 160 } 161 162 a := binary.BigEndian.Uint64(src) 163 b := binary.BigEndian.Uint64(src[8:]) 164 c := binary.BigEndian.Uint64(src[16:]) 165 166 return scanner.ScanLine(Line{ 167 A: math.Float64frombits(a), 168 B: math.Float64frombits(b), 169 C: math.Float64frombits(c), 170 Valid: true, 171 }) 172 } 173 174 type scanPlanTextAnyToLineScanner struct{} 175 176 func (scanPlanTextAnyToLineScanner) Scan(src []byte, dst any) error { 177 scanner := (dst).(LineScanner) 178 179 if src == nil { 180 return scanner.ScanLine(Line{}) 181 } 182 183 if len(src) < 7 { 184 return fmt.Errorf("invalid length for line: %v", len(src)) 185 } 186 187 parts := strings.SplitN(string(src[1:len(src)-1]), ",", 3) 188 if len(parts) < 3 { 189 return fmt.Errorf("invalid format for line") 190 } 191 192 a, err := strconv.ParseFloat(parts[0], 64) 193 if err != nil { 194 return err 195 } 196 197 b, err := strconv.ParseFloat(parts[1], 64) 198 if err != nil { 199 return err 200 } 201 202 c, err := strconv.ParseFloat(parts[2], 64) 203 if err != nil { 204 return err 205 } 206 207 return scanner.ScanLine(Line{A: a, B: b, C: c, Valid: true}) 208 } 209 210 func (c LineCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) { 211 return codecDecodeToTextFormat(c, m, oid, format, src) 212 } 213 214 func (c LineCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) { 215 if src == nil { 216 return nil, nil 217 } 218 219 var line Line 220 err := codecScan(c, m, oid, format, src, &line) 221 if err != nil { 222 return nil, err 223 } 224 return line, nil 225 }