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
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
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 }
226
View as plain text