1 package pgtype_test
2
3 import (
4 "context"
5 "reflect"
6 "testing"
7
8 "github.com/jackc/pgtype"
9 "github.com/jackc/pgtype/testutil"
10 "github.com/jackc/pgx/v4"
11 )
12
13 func TestHstoreArrayTranscode(t *testing.T) {
14 conn := testutil.MustConnectPgx(t)
15 defer testutil.MustCloseContext(t, conn)
16
17 var hstoreOID uint32
18 err := conn.QueryRow(context.Background(), "select t.oid from pg_type t where t.typname='hstore';").Scan(&hstoreOID)
19 if err != nil {
20 t.Fatalf("did not find hstore OID, %v", err)
21 }
22 conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.Hstore{}, Name: "hstore", OID: hstoreOID})
23
24 var hstoreArrayOID uint32
25 err = conn.QueryRow(context.Background(), "select t.oid from pg_type t where t.typname='_hstore';").Scan(&hstoreArrayOID)
26 if err != nil {
27 t.Fatalf("did not find _hstore OID, %v", err)
28 }
29 conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.HstoreArray{}, Name: "_hstore", OID: hstoreArrayOID})
30
31 text := func(s string) pgtype.Text {
32 return pgtype.Text{String: s, Status: pgtype.Present}
33 }
34
35 values := []pgtype.Hstore{
36 {Map: map[string]pgtype.Text{}, Status: pgtype.Present},
37 {Map: map[string]pgtype.Text{"foo": text("bar")}, Status: pgtype.Present},
38 {Map: map[string]pgtype.Text{"foo": text("bar"), "baz": text("quz")}, Status: pgtype.Present},
39 {Map: map[string]pgtype.Text{"NULL": text("bar")}, Status: pgtype.Present},
40 {Map: map[string]pgtype.Text{"foo": text("NULL")}, Status: pgtype.Present},
41 {Status: pgtype.Null},
42 }
43
44 specialStrings := []string{
45 `"`,
46 `'`,
47 `\`,
48 `\\`,
49 `=>`,
50 ` `,
51 `\ / / \\ => " ' " '`,
52 }
53 for _, s := range specialStrings {
54
55 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{s + "foo": text("bar")}, Status: pgtype.Present})
56 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s + "bar": text("bar")}, Status: pgtype.Present})
57 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo" + s: text("bar")}, Status: pgtype.Present})
58 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{s: text("bar")}, Status: pgtype.Present})
59
60
61 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s + "bar")}, Status: pgtype.Present})
62 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s + "bar")}, Status: pgtype.Present})
63 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text("foo" + s)}, Status: pgtype.Present})
64 values = append(values, pgtype.Hstore{Map: map[string]pgtype.Text{"foo": text(s)}, Status: pgtype.Present})
65 }
66
67 src := &pgtype.HstoreArray{
68 Elements: values,
69 Dimensions: []pgtype.ArrayDimension{{Length: int32(len(values)), LowerBound: 1}},
70 Status: pgtype.Present,
71 }
72
73 _, err = conn.Prepare(context.Background(), "test", "select $1::hstore[]")
74 if err != nil {
75 t.Fatal(err)
76 }
77
78 formats := []struct {
79 name string
80 formatCode int16
81 }{
82 {name: "TextFormat", formatCode: pgx.TextFormatCode},
83 {name: "BinaryFormat", formatCode: pgx.BinaryFormatCode},
84 }
85
86 for _, fc := range formats {
87 queryResultFormats := pgx.QueryResultFormats{fc.formatCode}
88 vEncoder := testutil.ForceEncoder(src, fc.formatCode)
89 if vEncoder == nil {
90 t.Logf("%#v does not implement %v", src, fc.name)
91 continue
92 }
93
94 var result pgtype.HstoreArray
95 err := conn.QueryRow(context.Background(), "test", queryResultFormats, vEncoder).Scan(&result)
96 if err != nil {
97 t.Errorf("%v: %v", fc.name, err)
98 continue
99 }
100
101 if result.Status != src.Status {
102 t.Errorf("%v: expected Status %v, got %v", fc.formatCode, src.Status, result.Status)
103 continue
104 }
105
106 if len(result.Elements) != len(src.Elements) {
107 t.Errorf("%v: expected %v elements, got %v", fc.formatCode, len(src.Elements), len(result.Elements))
108 continue
109 }
110
111 for i := range result.Elements {
112 a := src.Elements[i]
113 b := result.Elements[i]
114
115 if a.Status != b.Status {
116 t.Errorf("%v element idx %d: expected status %v, got %v", fc.formatCode, i, a.Status, b.Status)
117 }
118
119 if len(a.Map) != len(b.Map) {
120 t.Errorf("%v element idx %d: expected %v pairs, got %v", fc.formatCode, i, len(a.Map), len(b.Map))
121 }
122
123 for k := range a.Map {
124 if a.Map[k] != b.Map[k] {
125 t.Errorf("%v element idx %d: expected key %v to be %v, got %v", fc.formatCode, i, k, a.Map[k], b.Map[k])
126 }
127 }
128 }
129 }
130 }
131
132 func TestHstoreArraySet(t *testing.T) {
133 successfulTests := []struct {
134 src interface{}
135 result pgtype.HstoreArray
136 }{
137 {
138 src: []map[string]string{{"foo": "bar"}},
139 result: pgtype.HstoreArray{
140 Elements: []pgtype.Hstore{
141 {
142 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
143 Status: pgtype.Present,
144 },
145 },
146 Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
147 Status: pgtype.Present,
148 },
149 },
150 {
151 src: [][]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
152 result: pgtype.HstoreArray{
153 Elements: []pgtype.Hstore{
154 {
155 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
156 Status: pgtype.Present,
157 },
158 {
159 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
160 Status: pgtype.Present,
161 },
162 },
163 Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
164 Status: pgtype.Present,
165 },
166 },
167 {
168 src: [][][][]map[string]string{
169 {{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
170 {{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
171 result: pgtype.HstoreArray{
172 Elements: []pgtype.Hstore{
173 {
174 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
175 Status: pgtype.Present,
176 },
177 {
178 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
179 Status: pgtype.Present,
180 },
181 {
182 Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
183 Status: pgtype.Present,
184 },
185 {
186 Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
187 Status: pgtype.Present,
188 },
189 {
190 Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
191 Status: pgtype.Present,
192 },
193 {
194 Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
195 Status: pgtype.Present,
196 },
197 },
198 Dimensions: []pgtype.ArrayDimension{
199 {LowerBound: 1, Length: 2},
200 {LowerBound: 1, Length: 1},
201 {LowerBound: 1, Length: 1},
202 {LowerBound: 1, Length: 3}},
203 Status: pgtype.Present,
204 },
205 },
206 {
207 src: [2][1]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
208 result: pgtype.HstoreArray{
209 Elements: []pgtype.Hstore{
210 {
211 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
212 Status: pgtype.Present,
213 },
214 {
215 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
216 Status: pgtype.Present,
217 },
218 },
219 Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
220 Status: pgtype.Present,
221 },
222 },
223 {
224 src: [2][1][1][3]map[string]string{
225 {{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
226 {{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
227 result: pgtype.HstoreArray{
228 Elements: []pgtype.Hstore{
229 {
230 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
231 Status: pgtype.Present,
232 },
233 {
234 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
235 Status: pgtype.Present,
236 },
237 {
238 Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
239 Status: pgtype.Present,
240 },
241 {
242 Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
243 Status: pgtype.Present,
244 },
245 {
246 Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
247 Status: pgtype.Present,
248 },
249 {
250 Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
251 Status: pgtype.Present,
252 },
253 },
254 Dimensions: []pgtype.ArrayDimension{
255 {LowerBound: 1, Length: 2},
256 {LowerBound: 1, Length: 1},
257 {LowerBound: 1, Length: 1},
258 {LowerBound: 1, Length: 3}},
259 Status: pgtype.Present,
260 },
261 },
262 }
263
264 for i, tt := range successfulTests {
265 var dst pgtype.HstoreArray
266 err := dst.Set(tt.src)
267 if err != nil {
268 t.Errorf("%d: %v", i, err)
269 }
270
271 if !reflect.DeepEqual(dst, tt.result) {
272 t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst)
273 }
274 }
275 }
276
277 func TestHstoreArrayAssignTo(t *testing.T) {
278 var hstoreSlice []map[string]string
279 var hstoreSliceDim2 [][]map[string]string
280 var hstoreSliceDim4 [][][][]map[string]string
281 var hstoreArrayDim2 [2][1]map[string]string
282 var hstoreArrayDim4 [2][1][1][3]map[string]string
283
284 simpleTests := []struct {
285 src pgtype.HstoreArray
286 dst interface{}
287 expected interface{}
288 }{
289 {
290 src: pgtype.HstoreArray{
291 Elements: []pgtype.Hstore{
292 {
293 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
294 Status: pgtype.Present,
295 },
296 },
297 Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
298 Status: pgtype.Present,
299 },
300 dst: &hstoreSlice,
301 expected: []map[string]string{{"foo": "bar"}}},
302 {
303 src: pgtype.HstoreArray{Status: pgtype.Null}, dst: &hstoreSlice, expected: (([]map[string]string)(nil)),
304 },
305 {
306 src: pgtype.HstoreArray{Status: pgtype.Present}, dst: &hstoreSlice, expected: []map[string]string{},
307 },
308 {
309 src: pgtype.HstoreArray{
310 Elements: []pgtype.Hstore{
311 {
312 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
313 Status: pgtype.Present,
314 },
315 {
316 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
317 Status: pgtype.Present,
318 },
319 },
320 Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
321 Status: pgtype.Present,
322 },
323 dst: &hstoreSliceDim2,
324 expected: [][]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
325 },
326 {
327 src: pgtype.HstoreArray{
328 Elements: []pgtype.Hstore{
329 {
330 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
331 Status: pgtype.Present,
332 },
333 {
334 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
335 Status: pgtype.Present,
336 },
337 {
338 Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
339 Status: pgtype.Present,
340 },
341 {
342 Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
343 Status: pgtype.Present,
344 },
345 {
346 Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
347 Status: pgtype.Present,
348 },
349 {
350 Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
351 Status: pgtype.Present,
352 },
353 },
354 Dimensions: []pgtype.ArrayDimension{
355 {LowerBound: 1, Length: 2},
356 {LowerBound: 1, Length: 1},
357 {LowerBound: 1, Length: 1},
358 {LowerBound: 1, Length: 3}},
359 Status: pgtype.Present,
360 },
361 dst: &hstoreSliceDim4,
362 expected: [][][][]map[string]string{
363 {{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
364 {{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
365 },
366 {
367 src: pgtype.HstoreArray{
368 Elements: []pgtype.Hstore{
369 {
370 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
371 Status: pgtype.Present,
372 },
373 {
374 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
375 Status: pgtype.Present,
376 },
377 },
378 Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
379 Status: pgtype.Present,
380 },
381 dst: &hstoreArrayDim2,
382 expected: [2][1]map[string]string{{{"foo": "bar"}}, {{"baz": "quz"}}},
383 },
384 {
385 src: pgtype.HstoreArray{
386 Elements: []pgtype.Hstore{
387 {
388 Map: map[string]pgtype.Text{"foo": {String: "bar", Status: pgtype.Present}},
389 Status: pgtype.Present,
390 },
391 {
392 Map: map[string]pgtype.Text{"baz": {String: "quz", Status: pgtype.Present}},
393 Status: pgtype.Present,
394 },
395 {
396 Map: map[string]pgtype.Text{"bar": {String: "baz", Status: pgtype.Present}},
397 Status: pgtype.Present,
398 },
399 {
400 Map: map[string]pgtype.Text{"wibble": {String: "wobble", Status: pgtype.Present}},
401 Status: pgtype.Present,
402 },
403 {
404 Map: map[string]pgtype.Text{"wubble": {String: "wabble", Status: pgtype.Present}},
405 Status: pgtype.Present,
406 },
407 {
408 Map: map[string]pgtype.Text{"wabble": {String: "wobble", Status: pgtype.Present}},
409 Status: pgtype.Present,
410 },
411 },
412 Dimensions: []pgtype.ArrayDimension{
413 {LowerBound: 1, Length: 2},
414 {LowerBound: 1, Length: 1},
415 {LowerBound: 1, Length: 1},
416 {LowerBound: 1, Length: 3}},
417 Status: pgtype.Present,
418 },
419 dst: &hstoreArrayDim4,
420 expected: [2][1][1][3]map[string]string{
421 {{{{"foo": "bar"}, {"baz": "quz"}, {"bar": "baz"}}}},
422 {{{{"wibble": "wobble"}, {"wubble": "wabble"}, {"wabble": "wobble"}}}}},
423 },
424 }
425
426 for i, tt := range simpleTests {
427 err := tt.src.AssignTo(tt.dst)
428 if err != nil {
429 t.Errorf("%d: %v", i, err)
430 }
431
432 if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
433 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
434 }
435 }
436 }
437
View as plain text