github.com/jackc/pgx/v5@v5.5.5/pgtype/uuid.go (about) 1 package pgtype 2 3 import ( 4 "bytes" 5 "database/sql/driver" 6 "encoding/hex" 7 "fmt" 8 ) 9 10 type UUIDScanner interface { 11 ScanUUID(v UUID) error 12 } 13 14 type UUIDValuer interface { 15 UUIDValue() (UUID, error) 16 } 17 18 type UUID struct { 19 Bytes [16]byte 20 Valid bool 21 } 22 23 func (b *UUID) ScanUUID(v UUID) error { 24 *b = v 25 return nil 26 } 27 28 func (b UUID) UUIDValue() (UUID, error) { 29 return b, nil 30 } 31 32 // parseUUID converts a string UUID in standard form to a byte array. 33 func parseUUID(src string) (dst [16]byte, err error) { 34 switch len(src) { 35 case 36: 36 src = src[0:8] + src[9:13] + src[14:18] + src[19:23] + src[24:] 37 case 32: 38 // dashes already stripped, assume valid 39 default: 40 // assume invalid. 41 return dst, fmt.Errorf("cannot parse UUID %v", src) 42 } 43 44 buf, err := hex.DecodeString(src) 45 if err != nil { 46 return dst, err 47 } 48 49 copy(dst[:], buf) 50 return dst, err 51 } 52 53 // encodeUUID converts a uuid byte array to UUID standard string form. 54 func encodeUUID(src [16]byte) string { 55 var buf [36]byte 56 57 hex.Encode(buf[0:8], src[:4]) 58 buf[8] = '-' 59 hex.Encode(buf[9:13], src[4:6]) 60 buf[13] = '-' 61 hex.Encode(buf[14:18], src[6:8]) 62 buf[18] = '-' 63 hex.Encode(buf[19:23], src[8:10]) 64 buf[23] = '-' 65 hex.Encode(buf[24:], src[10:]) 66 67 return string(buf[:]) 68 } 69 70 // Scan implements the database/sql Scanner interface. 71 func (dst *UUID) Scan(src any) error { 72 if src == nil { 73 *dst = UUID{} 74 return nil 75 } 76 77 switch src := src.(type) { 78 case string: 79 buf, err := parseUUID(src) 80 if err != nil { 81 return err 82 } 83 *dst = UUID{Bytes: buf, Valid: true} 84 return nil 85 } 86 87 return fmt.Errorf("cannot scan %T", src) 88 } 89 90 // Value implements the database/sql/driver Valuer interface. 91 func (src UUID) Value() (driver.Value, error) { 92 if !src.Valid { 93 return nil, nil 94 } 95 96 return encodeUUID(src.Bytes), nil 97 } 98 99 func (src UUID) MarshalJSON() ([]byte, error) { 100 if !src.Valid { 101 return []byte("null"), nil 102 } 103 104 var buff bytes.Buffer 105 buff.WriteByte('"') 106 buff.WriteString(encodeUUID(src.Bytes)) 107 buff.WriteByte('"') 108 return buff.Bytes(), nil 109 } 110 111 func (dst *UUID) UnmarshalJSON(src []byte) error { 112 if bytes.Equal(src, []byte("null")) { 113 *dst = UUID{} 114 return nil 115 } 116 if len(src) != 38 { 117 return fmt.Errorf("invalid length for UUID: %v", len(src)) 118 } 119 buf, err := parseUUID(string(src[1 : len(src)-1])) 120 if err != nil { 121 return err 122 } 123 *dst = UUID{Bytes: buf, Valid: true} 124 return nil 125 } 126 127 type UUIDCodec struct{} 128 129 func (UUIDCodec) FormatSupported(format int16) bool { 130 return format == TextFormatCode || format == BinaryFormatCode 131 } 132 133 func (UUIDCodec) PreferredFormat() int16 { 134 return BinaryFormatCode 135 } 136 137 func (UUIDCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan { 138 if _, ok := value.(UUIDValuer); !ok { 139 return nil 140 } 141 142 switch format { 143 case BinaryFormatCode: 144 return encodePlanUUIDCodecBinaryUUIDValuer{} 145 case TextFormatCode: 146 return encodePlanUUIDCodecTextUUIDValuer{} 147 } 148 149 return nil 150 } 151 152 type encodePlanUUIDCodecBinaryUUIDValuer struct{} 153 154 func (encodePlanUUIDCodecBinaryUUIDValuer) Encode(value any, buf []byte) (newBuf []byte, err error) { 155 uuid, err := value.(UUIDValuer).UUIDValue() 156 if err != nil { 157 return nil, err 158 } 159 160 if !uuid.Valid { 161 return nil, nil 162 } 163 164 return append(buf, uuid.Bytes[:]...), nil 165 } 166 167 type encodePlanUUIDCodecTextUUIDValuer struct{} 168 169 func (encodePlanUUIDCodecTextUUIDValuer) Encode(value any, buf []byte) (newBuf []byte, err error) { 170 uuid, err := value.(UUIDValuer).UUIDValue() 171 if err != nil { 172 return nil, err 173 } 174 175 if !uuid.Valid { 176 return nil, nil 177 } 178 179 return append(buf, encodeUUID(uuid.Bytes)...), nil 180 } 181 182 func (UUIDCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { 183 switch format { 184 case BinaryFormatCode: 185 switch target.(type) { 186 case UUIDScanner: 187 return scanPlanBinaryUUIDToUUIDScanner{} 188 case TextScanner: 189 return scanPlanBinaryUUIDToTextScanner{} 190 } 191 case TextFormatCode: 192 switch target.(type) { 193 case UUIDScanner: 194 return scanPlanTextAnyToUUIDScanner{} 195 } 196 } 197 198 return nil 199 } 200 201 type scanPlanBinaryUUIDToUUIDScanner struct{} 202 203 func (scanPlanBinaryUUIDToUUIDScanner) Scan(src []byte, dst any) error { 204 scanner := (dst).(UUIDScanner) 205 206 if src == nil { 207 return scanner.ScanUUID(UUID{}) 208 } 209 210 if len(src) != 16 { 211 return fmt.Errorf("invalid length for UUID: %v", len(src)) 212 } 213 214 uuid := UUID{Valid: true} 215 copy(uuid.Bytes[:], src) 216 217 return scanner.ScanUUID(uuid) 218 } 219 220 type scanPlanBinaryUUIDToTextScanner struct{} 221 222 func (scanPlanBinaryUUIDToTextScanner) Scan(src []byte, dst any) error { 223 scanner := (dst).(TextScanner) 224 225 if src == nil { 226 return scanner.ScanText(Text{}) 227 } 228 229 if len(src) != 16 { 230 return fmt.Errorf("invalid length for UUID: %v", len(src)) 231 } 232 233 var buf [16]byte 234 copy(buf[:], src) 235 236 return scanner.ScanText(Text{String: encodeUUID(buf), Valid: true}) 237 } 238 239 type scanPlanTextAnyToUUIDScanner struct{} 240 241 func (scanPlanTextAnyToUUIDScanner) Scan(src []byte, dst any) error { 242 scanner := (dst).(UUIDScanner) 243 244 if src == nil { 245 return scanner.ScanUUID(UUID{}) 246 } 247 248 buf, err := parseUUID(string(src)) 249 if err != nil { 250 return err 251 } 252 253 return scanner.ScanUUID(UUID{Bytes: buf, Valid: true}) 254 } 255 256 func (c UUIDCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) { 257 if src == nil { 258 return nil, nil 259 } 260 261 var uuid UUID 262 err := codecScan(c, m, oid, format, src, &uuid) 263 if err != nil { 264 return nil, err 265 } 266 267 return encodeUUID(uuid.Bytes), nil 268 } 269 270 func (c UUIDCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) { 271 if src == nil { 272 return nil, nil 273 } 274 275 var uuid UUID 276 err := codecScan(c, m, oid, format, src, &uuid) 277 if err != nil { 278 return nil, err 279 } 280 return uuid.Bytes, nil 281 }