Skip to content

Commit 3f84e89

Browse files
authored
Merge pull request #2142 from jackc/add-xid8
Add xid8 type
2 parents cc05954 + 32a6b1b commit 3f84e89

File tree

4 files changed

+356
-0
lines changed

4 files changed

+356
-0
lines changed

pgtype/pgtype.go

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
XMLOID = 142
3030
XMLArrayOID = 143
3131
JSONArrayOID = 199
32+
XID8ArrayOID = 271
3233
PointOID = 600
3334
LsegOID = 601
3435
PathOID = 602
@@ -117,6 +118,7 @@ const (
117118
TstzmultirangeOID = 4534
118119
DatemultirangeOID = 4535
119120
Int8multirangeOID = 4536
121+
XID8OID = 5069
120122
Int4multirangeArrayOID = 6150
121123
NummultirangeArrayOID = 6151
122124
TsmultirangeArrayOID = 6152

pgtype/pgtype_default.go

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func initDefaultMap() {
9090
defaultMap.RegisterType(&Type{Name: "varbit", OID: VarbitOID, Codec: BitsCodec{}})
9191
defaultMap.RegisterType(&Type{Name: "varchar", OID: VarcharOID, Codec: TextCodec{}})
9292
defaultMap.RegisterType(&Type{Name: "xid", OID: XIDOID, Codec: Uint32Codec{}})
93+
defaultMap.RegisterType(&Type{Name: "xid8", OID: XID8OID, Codec: Uint64Codec{}})
9394
defaultMap.RegisterType(&Type{Name: "xml", OID: XMLOID, Codec: &XMLCodec{Marshal: xml.Marshal, Unmarshal: xml.Unmarshal}})
9495

9596
// Range types
@@ -155,6 +156,7 @@ func initDefaultMap() {
155156
defaultMap.RegisterType(&Type{Name: "_varbit", OID: VarbitArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[VarbitOID]}})
156157
defaultMap.RegisterType(&Type{Name: "_varchar", OID: VarcharArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[VarcharOID]}})
157158
defaultMap.RegisterType(&Type{Name: "_xid", OID: XIDArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[XIDOID]}})
159+
defaultMap.RegisterType(&Type{Name: "_xid8", OID: XID8ArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[XID8OID]}})
158160
defaultMap.RegisterType(&Type{Name: "_xml", OID: XMLArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[XMLOID]}})
159161

160162
// Integer types that directly map to a PostgreSQL type

pgtype/uint64.go

+322
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
package pgtype
2+
3+
import (
4+
"database/sql/driver"
5+
"encoding/binary"
6+
"fmt"
7+
"math"
8+
"strconv"
9+
10+
"github.com/jackc/pgx/v5/internal/pgio"
11+
)
12+
13+
type Uint64Scanner interface {
14+
ScanUint64(v Uint64) error
15+
}
16+
17+
type Uint64Valuer interface {
18+
Uint64Value() (Uint64, error)
19+
}
20+
21+
// Uint64 is the core type that is used to represent PostgreSQL types such as XID8.
22+
type Uint64 struct {
23+
Uint64 uint64
24+
Valid bool
25+
}
26+
27+
func (n *Uint64) ScanUint64(v Uint64) error {
28+
*n = v
29+
return nil
30+
}
31+
32+
func (n Uint64) Uint64Value() (Uint64, error) {
33+
return n, nil
34+
}
35+
36+
// Scan implements the database/sql Scanner interface.
37+
func (dst *Uint64) Scan(src any) error {
38+
if src == nil {
39+
*dst = Uint64{}
40+
return nil
41+
}
42+
43+
var n uint64
44+
45+
switch src := src.(type) {
46+
case int64:
47+
if src < 0 {
48+
return fmt.Errorf("%d is less than the minimum value for Uint64", src)
49+
}
50+
n = uint64(src)
51+
case string:
52+
un, err := strconv.ParseUint(src, 10, 64)
53+
if err != nil {
54+
return err
55+
}
56+
n = un
57+
default:
58+
return fmt.Errorf("cannot scan %T", src)
59+
}
60+
61+
*dst = Uint64{Uint64: n, Valid: true}
62+
63+
return nil
64+
}
65+
66+
// Value implements the database/sql/driver Valuer interface.
67+
func (src Uint64) Value() (driver.Value, error) {
68+
if !src.Valid {
69+
return nil, nil
70+
}
71+
72+
// If the value is greater than the maximum value for int64, return it as a string instead of losing data or returning
73+
// an error.
74+
if src.Uint64 > math.MaxInt64 {
75+
return strconv.FormatUint(src.Uint64, 10), nil
76+
}
77+
78+
return int64(src.Uint64), nil
79+
}
80+
81+
type Uint64Codec struct{}
82+
83+
func (Uint64Codec) FormatSupported(format int16) bool {
84+
return format == TextFormatCode || format == BinaryFormatCode
85+
}
86+
87+
func (Uint64Codec) PreferredFormat() int16 {
88+
return BinaryFormatCode
89+
}
90+
91+
func (Uint64Codec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
92+
switch format {
93+
case BinaryFormatCode:
94+
switch value.(type) {
95+
case uint64:
96+
return encodePlanUint64CodecBinaryUint64{}
97+
case Uint64Valuer:
98+
return encodePlanUint64CodecBinaryUint64Valuer{}
99+
case Int64Valuer:
100+
return encodePlanUint64CodecBinaryInt64Valuer{}
101+
}
102+
case TextFormatCode:
103+
switch value.(type) {
104+
case uint64:
105+
return encodePlanUint64CodecTextUint64{}
106+
case Int64Valuer:
107+
return encodePlanUint64CodecTextInt64Valuer{}
108+
}
109+
}
110+
111+
return nil
112+
}
113+
114+
type encodePlanUint64CodecBinaryUint64 struct{}
115+
116+
func (encodePlanUint64CodecBinaryUint64) Encode(value any, buf []byte) (newBuf []byte, err error) {
117+
v := value.(uint64)
118+
return pgio.AppendUint64(buf, v), nil
119+
}
120+
121+
type encodePlanUint64CodecBinaryUint64Valuer struct{}
122+
123+
func (encodePlanUint64CodecBinaryUint64Valuer) Encode(value any, buf []byte) (newBuf []byte, err error) {
124+
v, err := value.(Uint64Valuer).Uint64Value()
125+
if err != nil {
126+
return nil, err
127+
}
128+
129+
if !v.Valid {
130+
return nil, nil
131+
}
132+
133+
return pgio.AppendUint64(buf, v.Uint64), nil
134+
}
135+
136+
type encodePlanUint64CodecBinaryInt64Valuer struct{}
137+
138+
func (encodePlanUint64CodecBinaryInt64Valuer) Encode(value any, buf []byte) (newBuf []byte, err error) {
139+
v, err := value.(Int64Valuer).Int64Value()
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
if !v.Valid {
145+
return nil, nil
146+
}
147+
148+
if v.Int64 < 0 {
149+
return nil, fmt.Errorf("%d is less than minimum value for uint64", v.Int64)
150+
}
151+
152+
return pgio.AppendUint64(buf, uint64(v.Int64)), nil
153+
}
154+
155+
type encodePlanUint64CodecTextUint64 struct{}
156+
157+
func (encodePlanUint64CodecTextUint64) Encode(value any, buf []byte) (newBuf []byte, err error) {
158+
v := value.(uint64)
159+
return append(buf, strconv.FormatUint(uint64(v), 10)...), nil
160+
}
161+
162+
type encodePlanUint64CodecTextUint64Valuer struct{}
163+
164+
func (encodePlanUint64CodecTextUint64Valuer) Encode(value any, buf []byte) (newBuf []byte, err error) {
165+
v, err := value.(Uint64Valuer).Uint64Value()
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
if !v.Valid {
171+
return nil, nil
172+
}
173+
174+
return append(buf, strconv.FormatUint(v.Uint64, 10)...), nil
175+
}
176+
177+
type encodePlanUint64CodecTextInt64Valuer struct{}
178+
179+
func (encodePlanUint64CodecTextInt64Valuer) Encode(value any, buf []byte) (newBuf []byte, err error) {
180+
v, err := value.(Int64Valuer).Int64Value()
181+
if err != nil {
182+
return nil, err
183+
}
184+
185+
if !v.Valid {
186+
return nil, nil
187+
}
188+
189+
if v.Int64 < 0 {
190+
return nil, fmt.Errorf("%d is less than minimum value for uint64", v.Int64)
191+
}
192+
193+
return append(buf, strconv.FormatInt(v.Int64, 10)...), nil
194+
}
195+
196+
func (Uint64Codec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
197+
198+
switch format {
199+
case BinaryFormatCode:
200+
switch target.(type) {
201+
case *uint64:
202+
return scanPlanBinaryUint64ToUint64{}
203+
case Uint64Scanner:
204+
return scanPlanBinaryUint64ToUint64Scanner{}
205+
case TextScanner:
206+
return scanPlanBinaryUint64ToTextScanner{}
207+
}
208+
case TextFormatCode:
209+
switch target.(type) {
210+
case *uint64:
211+
return scanPlanTextAnyToUint64{}
212+
case Uint64Scanner:
213+
return scanPlanTextAnyToUint64Scanner{}
214+
}
215+
}
216+
217+
return nil
218+
}
219+
220+
func (c Uint64Codec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
221+
if src == nil {
222+
return nil, nil
223+
}
224+
225+
var n uint64
226+
err := codecScan(c, m, oid, format, src, &n)
227+
if err != nil {
228+
return nil, err
229+
}
230+
return int64(n), nil
231+
}
232+
233+
func (c Uint64Codec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
234+
if src == nil {
235+
return nil, nil
236+
}
237+
238+
var n uint64
239+
err := codecScan(c, m, oid, format, src, &n)
240+
if err != nil {
241+
return nil, err
242+
}
243+
return n, nil
244+
}
245+
246+
type scanPlanBinaryUint64ToUint64 struct{}
247+
248+
func (scanPlanBinaryUint64ToUint64) Scan(src []byte, dst any) error {
249+
if src == nil {
250+
return fmt.Errorf("cannot scan NULL into %T", dst)
251+
}
252+
253+
if len(src) != 8 {
254+
return fmt.Errorf("invalid length for uint64: %v", len(src))
255+
}
256+
257+
p := (dst).(*uint64)
258+
*p = binary.BigEndian.Uint64(src)
259+
260+
return nil
261+
}
262+
263+
type scanPlanBinaryUint64ToUint64Scanner struct{}
264+
265+
func (scanPlanBinaryUint64ToUint64Scanner) Scan(src []byte, dst any) error {
266+
s, ok := (dst).(Uint64Scanner)
267+
if !ok {
268+
return ErrScanTargetTypeChanged
269+
}
270+
271+
if src == nil {
272+
return s.ScanUint64(Uint64{})
273+
}
274+
275+
if len(src) != 8 {
276+
return fmt.Errorf("invalid length for uint64: %v", len(src))
277+
}
278+
279+
n := binary.BigEndian.Uint64(src)
280+
281+
return s.ScanUint64(Uint64{Uint64: n, Valid: true})
282+
}
283+
284+
type scanPlanBinaryUint64ToTextScanner struct{}
285+
286+
func (scanPlanBinaryUint64ToTextScanner) Scan(src []byte, dst any) error {
287+
s, ok := (dst).(TextScanner)
288+
if !ok {
289+
return ErrScanTargetTypeChanged
290+
}
291+
292+
if src == nil {
293+
return s.ScanText(Text{})
294+
}
295+
296+
if len(src) != 8 {
297+
return fmt.Errorf("invalid length for uint64: %v", len(src))
298+
}
299+
300+
n := uint64(binary.BigEndian.Uint64(src))
301+
return s.ScanText(Text{String: strconv.FormatUint(n, 10), Valid: true})
302+
}
303+
304+
type scanPlanTextAnyToUint64Scanner struct{}
305+
306+
func (scanPlanTextAnyToUint64Scanner) Scan(src []byte, dst any) error {
307+
s, ok := (dst).(Uint64Scanner)
308+
if !ok {
309+
return ErrScanTargetTypeChanged
310+
}
311+
312+
if src == nil {
313+
return s.ScanUint64(Uint64{})
314+
}
315+
316+
n, err := strconv.ParseUint(string(src), 10, 64)
317+
if err != nil {
318+
return err
319+
}
320+
321+
return s.ScanUint64(Uint64{Uint64: n, Valid: true})
322+
}

pgtype/uint64_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package pgtype_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/jackc/pgx/v5/pgtype"
8+
"github.com/jackc/pgx/v5/pgxtest"
9+
)
10+
11+
func TestUint64Codec(t *testing.T) {
12+
skipCockroachDB(t, "Server does not support xid8 (https://github.com/cockroachdb/cockroach/issues/36815)")
13+
skipPostgreSQLVersionLessThan(t, 13)
14+
15+
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "xid8", []pgxtest.ValueRoundTripTest{
16+
{
17+
pgtype.Uint64{Uint64: 1 << 36, Valid: true},
18+
new(pgtype.Uint64),
19+
isExpectedEq(pgtype.Uint64{Uint64: 1 << 36, Valid: true}),
20+
},
21+
{pgtype.Uint64{}, new(pgtype.Uint64), isExpectedEq(pgtype.Uint64{})},
22+
{nil, new(pgtype.Uint64), isExpectedEq(pgtype.Uint64{})},
23+
{
24+
uint64(1 << 36),
25+
new(uint64),
26+
isExpectedEq(uint64(1 << 36)),
27+
},
28+
{"1147", new(string), isExpectedEq("1147")},
29+
})
30+
}

0 commit comments

Comments
 (0)