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