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 PathScanner interface {
15 ScanPath(v Path) error
16 }
17
18 type PathValuer interface {
19 PathValue() (Path, error)
20 }
21
22 type Path struct {
23 P []Vec2
24 Closed bool
25 Valid bool
26 }
27
28 func (path *Path) ScanPath(v Path) error {
29 *path = v
30 return nil
31 }
32
33 func (path Path) PathValue() (Path, error) {
34 return path, nil
35 }
36
37
38 func (path *Path) Scan(src any) error {
39 if src == nil {
40 *path = Path{}
41 return nil
42 }
43
44 switch src := src.(type) {
45 case string:
46 return scanPlanTextAnyToPathScanner{}.Scan([]byte(src), path)
47 }
48
49 return fmt.Errorf("cannot scan %T", src)
50 }
51
52
53 func (path Path) Value() (driver.Value, error) {
54 if !path.Valid {
55 return nil, nil
56 }
57
58 buf, err := PathCodec{}.PlanEncode(nil, 0, TextFormatCode, path).Encode(path, nil)
59 if err != nil {
60 return nil, err
61 }
62
63 return string(buf), err
64 }
65
66 type PathCodec struct{}
67
68 func (PathCodec) FormatSupported(format int16) bool {
69 return format == TextFormatCode || format == BinaryFormatCode
70 }
71
72 func (PathCodec) PreferredFormat() int16 {
73 return BinaryFormatCode
74 }
75
76 func (PathCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
77 if _, ok := value.(PathValuer); !ok {
78 return nil
79 }
80
81 switch format {
82 case BinaryFormatCode:
83 return encodePlanPathCodecBinary{}
84 case TextFormatCode:
85 return encodePlanPathCodecText{}
86 }
87
88 return nil
89 }
90
91 type encodePlanPathCodecBinary struct{}
92
93 func (encodePlanPathCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
94 path, err := value.(PathValuer).PathValue()
95 if err != nil {
96 return nil, err
97 }
98
99 if !path.Valid {
100 return nil, nil
101 }
102
103 var closeByte byte
104 if path.Closed {
105 closeByte = 1
106 }
107 buf = append(buf, closeByte)
108
109 buf = pgio.AppendInt32(buf, int32(len(path.P)))
110
111 for _, p := range path.P {
112 buf = pgio.AppendUint64(buf, math.Float64bits(p.X))
113 buf = pgio.AppendUint64(buf, math.Float64bits(p.Y))
114 }
115
116 return buf, nil
117 }
118
119 type encodePlanPathCodecText struct{}
120
121 func (encodePlanPathCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
122 path, err := value.(PathValuer).PathValue()
123 if err != nil {
124 return nil, err
125 }
126
127 if !path.Valid {
128 return nil, nil
129 }
130
131 var startByte, endByte byte
132 if path.Closed {
133 startByte = '('
134 endByte = ')'
135 } else {
136 startByte = '['
137 endByte = ']'
138 }
139 buf = append(buf, startByte)
140
141 for i, p := range path.P {
142 if i > 0 {
143 buf = append(buf, ',')
144 }
145 buf = append(buf, fmt.Sprintf(`(%s,%s)`,
146 strconv.FormatFloat(p.X, 'f', -1, 64),
147 strconv.FormatFloat(p.Y, 'f', -1, 64),
148 )...)
149 }
150
151 buf = append(buf, endByte)
152
153 return buf, nil
154 }
155
156 func (PathCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
157
158 switch format {
159 case BinaryFormatCode:
160 switch target.(type) {
161 case PathScanner:
162 return scanPlanBinaryPathToPathScanner{}
163 }
164 case TextFormatCode:
165 switch target.(type) {
166 case PathScanner:
167 return scanPlanTextAnyToPathScanner{}
168 }
169 }
170
171 return nil
172 }
173
174 type scanPlanBinaryPathToPathScanner struct{}
175
176 func (scanPlanBinaryPathToPathScanner) Scan(src []byte, dst any) error {
177 scanner := (dst).(PathScanner)
178
179 if src == nil {
180 return scanner.ScanPath(Path{})
181 }
182
183 if len(src) < 5 {
184 return fmt.Errorf("invalid length for Path: %v", len(src))
185 }
186
187 closed := src[0] == 1
188 pointCount := int(binary.BigEndian.Uint32(src[1:]))
189
190 rp := 5
191
192 if 5+pointCount*16 != len(src) {
193 return fmt.Errorf("invalid length for Path with %d points: %v", pointCount, len(src))
194 }
195
196 points := make([]Vec2, pointCount)
197 for i := 0; i < len(points); i++ {
198 x := binary.BigEndian.Uint64(src[rp:])
199 rp += 8
200 y := binary.BigEndian.Uint64(src[rp:])
201 rp += 8
202 points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)}
203 }
204
205 return scanner.ScanPath(Path{
206 P: points,
207 Closed: closed,
208 Valid: true,
209 })
210 }
211
212 type scanPlanTextAnyToPathScanner struct{}
213
214 func (scanPlanTextAnyToPathScanner) Scan(src []byte, dst any) error {
215 scanner := (dst).(PathScanner)
216
217 if src == nil {
218 return scanner.ScanPath(Path{})
219 }
220
221 if len(src) < 7 {
222 return fmt.Errorf("invalid length for Path: %v", len(src))
223 }
224
225 closed := src[0] == '('
226 points := make([]Vec2, 0)
227
228 str := string(src[2:])
229
230 for {
231 end := strings.IndexByte(str, ',')
232 x, err := strconv.ParseFloat(str[:end], 64)
233 if err != nil {
234 return err
235 }
236
237 str = str[end+1:]
238 end = strings.IndexByte(str, ')')
239
240 y, err := strconv.ParseFloat(str[:end], 64)
241 if err != nil {
242 return err
243 }
244
245 points = append(points, Vec2{x, y})
246
247 if end+3 < len(str) {
248 str = str[end+3:]
249 } else {
250 break
251 }
252 }
253
254 return scanner.ScanPath(Path{P: points, Closed: closed, Valid: true})
255 }
256
257 func (c PathCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
258 return codecDecodeToTextFormat(c, m, oid, format, src)
259 }
260
261 func (c PathCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
262 if src == nil {
263 return nil, nil
264 }
265
266 var path Path
267 err := codecScan(c, m, oid, format, src, &path)
268 if err != nil {
269 return nil, err
270 }
271 return path, nil
272 }
273
View as plain text