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