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