1 package pgtype_test
2
3 import (
4 "context"
5 "database/sql"
6 "database/sql/driver"
7 "encoding/json"
8 "errors"
9 "testing"
10
11 pgx "github.com/jackc/pgx/v5"
12 "github.com/jackc/pgx/v5/pgxtest"
13 "github.com/stretchr/testify/require"
14 )
15
16 func isExpectedEqMap(a any) func(any) bool {
17 return func(v any) bool {
18 aa := a.(map[string]any)
19 bb := v.(map[string]any)
20
21 if (aa == nil) != (bb == nil) {
22 return false
23 }
24
25 if aa == nil {
26 return true
27 }
28
29 if len(aa) != len(bb) {
30 return false
31 }
32
33 for k := range aa {
34 if aa[k] != bb[k] {
35 return false
36 }
37 }
38
39 return true
40 }
41 }
42
43 func TestJSONCodec(t *testing.T) {
44 type jsonStruct struct {
45 Name string `json:"name"`
46 Age int `json:"age"`
47 }
48
49 pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "json", []pgxtest.ValueRoundTripTest{
50 {nil, new(*jsonStruct), isExpectedEq((*jsonStruct)(nil))},
51 {map[string]any(nil), new(*string), isExpectedEq((*string)(nil))},
52 {map[string]any(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
53 {[]byte(nil), new([]byte), isExpectedEqBytes([]byte(nil))},
54 {nil, new([]byte), isExpectedEqBytes([]byte(nil))},
55
56
57 {"42", new(sql.NullInt64), isExpectedEq(sql.NullInt64{Int64: 42, Valid: true})},
58
59
60 {sql.NullInt64{Int64: 42, Valid: true}, new(sql.NullInt64), isExpectedEq(sql.NullInt64{Int64: 42, Valid: true})},
61
62
63 {Issue1805(7), new(Issue1805), isExpectedEq(Issue1805(7))},
64 })
65
66 pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, pgxtest.KnownOIDQueryExecModes, "json", []pgxtest.ValueRoundTripTest{
67 {[]byte("{}"), new([]byte), isExpectedEqBytes([]byte("{}"))},
68 {[]byte("null"), new([]byte), isExpectedEqBytes([]byte("null"))},
69 {[]byte("42"), new([]byte), isExpectedEqBytes([]byte("42"))},
70 {[]byte(`"hello"`), new([]byte), isExpectedEqBytes([]byte(`"hello"`))},
71 {[]byte(`"hello"`), new(string), isExpectedEq(`"hello"`)},
72 {map[string]any{"foo": "bar"}, new(map[string]any), isExpectedEqMap(map[string]any{"foo": "bar"})},
73 {jsonStruct{Name: "Adam", Age: 10}, new(jsonStruct), isExpectedEq(jsonStruct{Name: "Adam", Age: 10})},
74 })
75 }
76
77 type Issue1805 int
78
79 func (i *Issue1805) Scan(src any) error {
80 var source []byte
81 switch src.(type) {
82 case string:
83 source = []byte(src.(string))
84 case []byte:
85 source = src.([]byte)
86 default:
87 return errors.New("unknown source type")
88 }
89 var newI int
90 if err := json.Unmarshal(source, &newI); err != nil {
91 return err
92 }
93 *i = Issue1805(newI)
94 return nil
95 }
96
97 func (i Issue1805) Value() (driver.Value, error) {
98 b, err := json.Marshal(int(i))
99 return string(b), err
100 }
101
102 func (i Issue1805) UnmarshalJSON(bytes []byte) error {
103 return errors.New("UnmarshalJSON called")
104 }
105
106 func (i Issue1805) MarshalJSON() ([]byte, error) {
107 return nil, errors.New("MarshalJSON called")
108 }
109
110
111 func TestJSONCodecUnmarshalSQLNull(t *testing.T) {
112 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
113
114 slice := []string{"foo", "bar", "baz"}
115 err := conn.QueryRow(ctx, "select null::json").Scan(&slice)
116 require.NoError(t, err)
117 require.Nil(t, slice)
118
119
120 m := map[string]any{"foo": "bar"}
121 err = conn.QueryRow(ctx, "select null::json").Scan(&m)
122 require.NoError(t, err)
123 require.Nil(t, m)
124
125 m = map[string]interface{}{"foo": "bar"}
126 err = conn.QueryRow(ctx, "select null::json").Scan(&m)
127 require.NoError(t, err)
128 require.Nil(t, m)
129
130
131 n := 42
132 p := &n
133 err = conn.QueryRow(ctx, "select null::json").Scan(&p)
134 require.NoError(t, err)
135 require.Nil(t, p)
136
137
138 str := "foobar"
139 err = conn.QueryRow(ctx, "select null::json").Scan(&str)
140 require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *string")
141
142
143 err = conn.QueryRow(ctx, "select null::json").Scan(&n)
144 require.EqualError(t, err, "can't scan into dest[0]: cannot scan NULL into *int")
145 })
146 }
147
148
149 func TestJSONCodecPointerToPointerToString(t *testing.T) {
150 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
151 var s *string
152 err := conn.QueryRow(ctx, "select '{}'::json").Scan(&s)
153 require.NoError(t, err)
154 require.NotNil(t, s)
155 require.Equal(t, "{}", *s)
156
157 err = conn.QueryRow(ctx, "select null::json").Scan(&s)
158 require.NoError(t, err)
159 require.Nil(t, s)
160 })
161 }
162
163
164 func TestJSONCodecPointerToPointerToInt(t *testing.T) {
165 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
166 n := 44
167 p := &n
168 err := conn.QueryRow(ctx, "select 'null'::jsonb").Scan(&p)
169 require.NoError(t, err)
170 require.Nil(t, p)
171 })
172 }
173
174
175 func TestJSONCodecPointerToPointerToStruct(t *testing.T) {
176 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
177 type ImageSize struct {
178 Height int `json:"height"`
179 Width int `json:"width"`
180 Str string `json:"str"`
181 }
182 is := &ImageSize{Height: 100, Width: 100, Str: "str"}
183 err := conn.QueryRow(ctx, `select 'null'::jsonb`).Scan(&is)
184 require.NoError(t, err)
185 require.Nil(t, is)
186 })
187 }
188
189 func TestJSONCodecClearExistingValueBeforeUnmarshal(t *testing.T) {
190 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
191 m := map[string]any{}
192 err := conn.QueryRow(ctx, `select '{"foo": "bar"}'::json`).Scan(&m)
193 require.NoError(t, err)
194 require.Equal(t, map[string]any{"foo": "bar"}, m)
195
196 err = conn.QueryRow(ctx, `select '{"baz": "quz"}'::json`).Scan(&m)
197 require.NoError(t, err)
198 require.Equal(t, map[string]any{"baz": "quz"}, m)
199 })
200 }
201
202 type ParentIssue1681 struct {
203 Child ChildIssue1681
204 }
205
206 func (t *ParentIssue1681) MarshalJSON() ([]byte, error) {
207 return []byte(`{"custom":"thing"}`), nil
208 }
209
210 type ChildIssue1681 struct{}
211
212 func (t ChildIssue1681) MarshalJSON() ([]byte, error) {
213 return []byte(`{"someVal": false}`), nil
214 }
215
216
217 func TestJSONCodecEncodeJSONMarshalerThatCanBeWrapped(t *testing.T) {
218 skipCockroachDB(t, "CockroachDB treats json as jsonb. This causes it to format differently than PostgreSQL.")
219
220 defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
221 var jsonStr string
222 err := conn.QueryRow(context.Background(), "select $1::json", &ParentIssue1681{}).Scan(&jsonStr)
223 require.NoError(t, err)
224 require.Equal(t, `{"custom":"thing"}`, jsonStr)
225 })
226 }
227
View as plain text