github.com/jackc/pgx/v5@v5.5.5/pgtype/box.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 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 // Scan implements the database/sql Scanner interface. 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 // Value implements the database/sql/driver Valuer interface. 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 }