1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package bigquery
16
17 import (
18 "encoding/base64"
19 "errors"
20 "fmt"
21 "math"
22 "math/big"
23 "reflect"
24 "testing"
25 "time"
26
27 "cloud.google.com/go/civil"
28 "cloud.google.com/go/internal/testutil"
29 "github.com/google/go-cmp/cmp"
30 bq "google.golang.org/api/bigquery/v2"
31 )
32
33 func TestConvertBasicValues(t *testing.T) {
34 schema := Schema{
35 {Type: StringFieldType},
36 {Type: IntegerFieldType},
37 {Type: FloatFieldType},
38 {Type: BooleanFieldType},
39 {Type: BytesFieldType},
40 {Type: NumericFieldType},
41 {Type: BigNumericFieldType},
42 {Type: GeographyFieldType},
43 {Type: JSONFieldType},
44 }
45 row := &bq.TableRow{
46 F: []*bq.TableCell{
47 {V: "a"},
48 {V: "1"},
49 {V: "1.2"},
50 {V: "true"},
51 {V: base64.StdEncoding.EncodeToString([]byte("foo"))},
52 {V: "123.123456789"},
53 {V: "99999999999999999999999999999999999999.99999999999999999999999999999999999999"},
54 {V: testGeography},
55 {V: "{\"alpha\": \"beta\"}"},
56 },
57 }
58 got, err := convertRow(row, schema)
59 if err != nil {
60 t.Fatalf("error converting: %v", err)
61 }
62
63 bigRatVal := new(big.Rat)
64 bigRatVal.SetString("99999999999999999999999999999999999999.99999999999999999999999999999999999999")
65 want := []Value{"a", int64(1), 1.2, true, []byte("foo"), big.NewRat(123123456789, 1e9), bigRatVal, testGeography, "{\"alpha\": \"beta\"}"}
66 if !testutil.Equal(got, want) {
67 t.Errorf("converting basic values: got:\n%v\nwant:\n%v", got, want)
68 }
69 }
70
71 func TestConvertTime(t *testing.T) {
72 schema := Schema{
73 {Type: TimestampFieldType},
74 {Type: DateFieldType},
75 {Type: TimeFieldType},
76 {Type: DateTimeFieldType},
77 {Type: RangeFieldType, RangeElementType: &RangeElementType{Type: TimestampFieldType}},
78 }
79 ts := testTimestamp.Round(time.Millisecond)
80 row := &bq.TableRow{
81 F: []*bq.TableCell{
82 {V: fmt.Sprint(ts.UnixMicro())},
83 {V: testDate.String()},
84 {V: testTime.String()},
85 {V: testDateTime.String()},
86 {V: fmt.Sprintf("[UNBOUNDED, %d)", ts.UnixMicro())},
87 },
88 }
89 got, err := convertRow(row, schema)
90 if err != nil {
91 t.Fatalf("error converting: %v", err)
92 }
93 want := []Value{ts, testDate, testTime, testDateTime, &RangeValue{End: ts}}
94 for i, g := range got {
95 w := want[i]
96 if !testutil.Equal(g, w) {
97 t.Errorf("#%d: got:\n%v\nwant:\n%v", i, g, w)
98 }
99 }
100
101
102 if gotTZ := got[0].(time.Time).Location(); gotTZ != time.UTC {
103 t.Errorf("expected time zone UTC: got:\n%v", gotTZ)
104 }
105 }
106
107 func TestConvertSmallTimes(t *testing.T) {
108 for _, year := range []int{1600, 1066, 1} {
109 want := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
110 s := fmt.Sprint(time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC).UnixMicro())
111 got, err := convertBasicType(s, TimestampFieldType)
112 if err != nil {
113 t.Fatal(err)
114 }
115 if !got.(time.Time).Equal(want) {
116 t.Errorf("got %v, want %v", got, want)
117 }
118 }
119 }
120
121 func TestConvertNullValues(t *testing.T) {
122 schema := Schema{{Type: StringFieldType}}
123 row := &bq.TableRow{
124 F: []*bq.TableCell{
125 {V: nil},
126 },
127 }
128 got, err := convertRow(row, schema)
129 if err != nil {
130 t.Fatalf("error converting: %v", err)
131 }
132 want := []Value{nil}
133 if !testutil.Equal(got, want) {
134 t.Errorf("converting null values: got:\n%v\nwant:\n%v", got, want)
135 }
136 }
137
138 func TestBasicRepetition(t *testing.T) {
139 schema := Schema{
140 {Type: IntegerFieldType, Repeated: true},
141 }
142 row := &bq.TableRow{
143 F: []*bq.TableCell{
144 {
145 V: []interface{}{
146 map[string]interface{}{
147 "v": "1",
148 },
149 map[string]interface{}{
150 "v": "2",
151 },
152 map[string]interface{}{
153 "v": "3",
154 },
155 },
156 },
157 },
158 }
159 got, err := convertRow(row, schema)
160 if err != nil {
161 t.Fatalf("error converting: %v", err)
162 }
163 want := []Value{[]Value{int64(1), int64(2), int64(3)}}
164 if !testutil.Equal(got, want) {
165 t.Errorf("converting basic repeated values: got:\n%v\nwant:\n%v", got, want)
166 }
167 }
168
169 func TestNestedRecordContainingRepetition(t *testing.T) {
170 schema := Schema{
171 {
172 Type: RecordFieldType,
173 Schema: Schema{
174 {Type: IntegerFieldType, Repeated: true},
175 },
176 },
177 }
178 row := &bq.TableRow{
179 F: []*bq.TableCell{
180 {
181 V: map[string]interface{}{
182 "f": []interface{}{
183 map[string]interface{}{
184 "v": []interface{}{
185 map[string]interface{}{"v": "1"},
186 map[string]interface{}{"v": "2"},
187 map[string]interface{}{"v": "3"},
188 },
189 },
190 },
191 },
192 },
193 },
194 }
195
196 got, err := convertRow(row, schema)
197 if err != nil {
198 t.Fatalf("error converting: %v", err)
199 }
200 want := []Value{[]Value{[]Value{int64(1), int64(2), int64(3)}}}
201 if !testutil.Equal(got, want) {
202 t.Errorf("converting basic repeated values: got:\n%v\nwant:\n%v", got, want)
203 }
204 }
205
206 func TestRepeatedRecordContainingRepetition(t *testing.T) {
207 schema := Schema{
208 {
209 Type: RecordFieldType,
210 Repeated: true,
211 Schema: Schema{
212 {Type: IntegerFieldType, Repeated: true},
213 },
214 },
215 }
216 row := &bq.TableRow{F: []*bq.TableCell{
217 {
218 V: []interface{}{
219 map[string]interface{}{
220 "v": map[string]interface{}{
221 "f": []interface{}{
222 map[string]interface{}{
223 "v": []interface{}{
224 map[string]interface{}{
225 "v": "1",
226 },
227 map[string]interface{}{
228 "v": "2",
229 },
230 map[string]interface{}{
231 "v": "3",
232 },
233 },
234 },
235 },
236 },
237 },
238 map[string]interface{}{
239 "v": map[string]interface{}{
240 "f": []interface{}{
241 map[string]interface{}{
242 "v": []interface{}{
243 map[string]interface{}{
244 "v": "4",
245 },
246 map[string]interface{}{
247 "v": "5",
248 },
249 map[string]interface{}{
250 "v": "6",
251 },
252 },
253 },
254 },
255 },
256 },
257 },
258 },
259 }}
260
261 got, err := convertRow(row, schema)
262 if err != nil {
263 t.Fatalf("error converting: %v", err)
264 }
265 want := []Value{
266 []Value{
267 []Value{
268 []Value{int64(1), int64(2), int64(3)},
269 },
270 []Value{
271 []Value{int64(4), int64(5), int64(6)},
272 },
273 },
274 }
275 if !testutil.Equal(got, want) {
276 t.Errorf("converting repeated records with repeated values: got:\n%v\nwant:\n%v", got, want)
277 }
278 }
279
280 func TestRepeatedRecordContainingRecord(t *testing.T) {
281 schema := Schema{
282 {
283 Type: RecordFieldType,
284 Repeated: true,
285 Schema: Schema{
286 {
287 Type: StringFieldType,
288 },
289 {
290 Type: RecordFieldType,
291 Schema: Schema{
292 {Type: IntegerFieldType},
293 {Type: StringFieldType},
294 },
295 },
296 },
297 },
298 }
299 row := &bq.TableRow{F: []*bq.TableCell{
300 {
301 V: []interface{}{
302 map[string]interface{}{
303 "v": map[string]interface{}{
304 "f": []interface{}{
305 map[string]interface{}{
306 "v": "first repeated record",
307 },
308 map[string]interface{}{
309 "v": map[string]interface{}{
310 "f": []interface{}{
311 map[string]interface{}{
312 "v": "1",
313 },
314 map[string]interface{}{
315 "v": "two",
316 },
317 },
318 },
319 },
320 },
321 },
322 },
323 map[string]interface{}{
324 "v": map[string]interface{}{
325 "f": []interface{}{
326 map[string]interface{}{
327 "v": "second repeated record",
328 },
329 map[string]interface{}{
330 "v": map[string]interface{}{
331 "f": []interface{}{
332 map[string]interface{}{
333 "v": "3",
334 },
335 map[string]interface{}{
336 "v": "four",
337 },
338 },
339 },
340 },
341 },
342 },
343 },
344 },
345 },
346 }}
347
348 got, err := convertRow(row, schema)
349 if err != nil {
350 t.Fatalf("error converting: %v", err)
351 }
352
353 want := []Value{
354 []Value{
355 []Value{
356 "first repeated record",
357 []Value{
358 int64(1),
359 "two",
360 },
361 },
362 []Value{
363 "second repeated record",
364 []Value{
365 int64(3),
366 "four",
367 },
368 },
369 },
370 }
371 if !testutil.Equal(got, want) {
372 t.Errorf("converting repeated records containing record : got:\n%v\nwant:\n%v", got, want)
373 }
374 }
375
376 func TestConvertRowErrors(t *testing.T) {
377
378 if _, err := convertRow(&bq.TableRow{F: []*bq.TableCell{{V: ""}}}, Schema{}); err == nil {
379 t.Error("got nil, want error")
380 }
381 v3 := map[string]interface{}{"v": 3}
382 for _, test := range []struct {
383 value interface{}
384 fs FieldSchema
385 }{
386 {3, FieldSchema{Type: IntegerFieldType}},
387 {[]interface{}{v3},
388 FieldSchema{Type: IntegerFieldType, Repeated: true}},
389 {map[string]interface{}{"f": []interface{}{v3}},
390 FieldSchema{Type: RecordFieldType, Schema: Schema{{Type: IntegerFieldType}}}},
391 {map[string]interface{}{"f": []interface{}{v3}},
392 FieldSchema{Type: RecordFieldType, Schema: Schema{}}},
393 } {
394 _, err := convertRow(
395 &bq.TableRow{F: []*bq.TableCell{{V: test.value}}},
396 Schema{&test.fs})
397 if err == nil {
398 t.Errorf("value %v, fs %v: got nil, want error", test.value, test.fs)
399 }
400 }
401
402
403 if _, err := convertBasicType("", FieldType("BAD")); err == nil {
404 t.Error("got nil, want error")
405 }
406 }
407
408 func TestValuesSaverConvertsToMap(t *testing.T) {
409 testCases := []struct {
410 name string
411 vs ValuesSaver
412 wantInsertID string
413 wantRow map[string]Value
414 }{
415 {
416 name: "scalars",
417 vs: ValuesSaver{
418 Schema: Schema{
419 {Name: "intField", Type: IntegerFieldType},
420 {Name: "strField", Type: StringFieldType},
421 {Name: "dtField", Type: DateTimeFieldType},
422 {Name: "nField", Type: NumericFieldType},
423 {Name: "bigNumField", Type: BigNumericFieldType},
424 {Name: "geoField", Type: GeographyFieldType},
425 },
426 InsertID: "iid",
427 Row: []Value{1, "a",
428 civil.DateTime{
429 Date: civil.Date{Year: 1, Month: 2, Day: 3},
430 Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 7000}},
431 big.NewRat(123456789000, 1e9),
432 big.NewRat(1, 3),
433 testGeography,
434 },
435 },
436 wantInsertID: "iid",
437 wantRow: map[string]Value{
438 "intField": 1,
439 "strField": "a",
440 "dtField": "0001-02-03 04:05:06.000007",
441 "nField": "123.456789000",
442 "bigNumField": "0.33333333333333333333333333333333333333",
443 "geoField": testGeography,
444 },
445 },
446 {
447 name: "intNested",
448 vs: ValuesSaver{
449 Schema: Schema{
450 {Name: "intField", Type: IntegerFieldType},
451 {
452 Name: "recordField",
453 Type: RecordFieldType,
454 Schema: Schema{
455 {Name: "nestedInt", Type: IntegerFieldType, Repeated: true},
456 },
457 },
458 },
459 InsertID: "iid",
460 Row: []Value{1, []Value{[]Value{2, 3}}},
461 },
462 wantInsertID: "iid",
463 wantRow: map[string]Value{
464 "intField": 1,
465 "recordField": map[string]Value{
466 "nestedInt": []Value{2, 3},
467 },
468 },
469 },
470 {
471 name: "nestedArray",
472 vs: ValuesSaver{
473 Schema: Schema{
474 {
475 Name: "records",
476 Type: RecordFieldType,
477 Schema: Schema{
478 {Name: "x", Type: IntegerFieldType},
479 {Name: "y", Type: IntegerFieldType},
480 },
481 Repeated: true,
482 },
483 },
484 InsertID: "iid",
485 Row: []Value{
486 []Value{
487 []Value{1, 2},
488 []Value{3, 4},
489 },
490 },
491 },
492 wantInsertID: "iid",
493 wantRow: map[string]Value{
494 "records": []Value{
495 map[string]Value{"x": 1, "y": 2},
496 map[string]Value{"x": 3, "y": 4},
497 },
498 },
499 },
500 {
501 name: "emptyNestedArray",
502 vs: ValuesSaver{
503 Schema: Schema{
504 {
505 Name: "records",
506 Type: RecordFieldType,
507 Schema: Schema{
508 {Name: "x", Type: IntegerFieldType},
509 {Name: "y", Type: IntegerFieldType},
510 },
511 Repeated: true,
512 },
513 },
514 InsertID: "iid",
515 Row: []Value{
516 []Value{},
517 },
518 },
519 wantInsertID: "iid",
520 wantRow: map[string]Value{
521 "records": []Value{},
522 },
523 },
524 }
525 for _, tc := range testCases {
526 t.Run(tc.name, func(t *testing.T) {
527 gotRow, gotInsertID, err := tc.vs.Save()
528 if err != nil {
529 t.Fatalf("Expected successful save; got: %v", err)
530 }
531 if !testutil.Equal(gotRow, tc.wantRow) {
532 t.Errorf("%v row:\ngot:\n%+v\nwant:\n%+v", tc.vs, gotRow, tc.wantRow)
533 }
534 if !testutil.Equal(gotInsertID, tc.wantInsertID) {
535 t.Errorf("%v ID:\ngot:\n%+v\nwant:\n%+v", tc.vs, gotInsertID, tc.wantInsertID)
536 }
537 })
538 }
539 }
540
541 func TestValuesToMapErrors(t *testing.T) {
542 for _, test := range []struct {
543 values []Value
544 schema Schema
545 }{
546 {
547 []Value{1},
548 Schema{},
549 },
550 {
551 []Value{1},
552 Schema{{Type: RecordFieldType}},
553 },
554 {
555 []Value{[]Value{1}},
556 Schema{{Type: RecordFieldType}},
557 },
558 {
559 []Value{[]Value{1}},
560 Schema{{Type: RecordFieldType, Repeated: true}},
561 },
562 {
563 []Value{[]Value{[]Value{1}}},
564 Schema{{Type: RecordFieldType, Repeated: true}},
565 },
566 } {
567 _, err := valuesToMap(test.values, test.schema)
568 if err == nil {
569 t.Errorf("%v, %v: got nil, want error", test.values, test.schema)
570 }
571 }
572 }
573
574 func TestStructSaver(t *testing.T) {
575 schema := Schema{
576 {Name: "s", Type: StringFieldType},
577 {Name: "r", Type: IntegerFieldType, Repeated: true},
578 {Name: "t", Type: TimeFieldType},
579 {Name: "tr", Type: TimeFieldType, Repeated: true},
580 {Name: "nested", Type: RecordFieldType, Schema: Schema{
581 {Name: "b", Type: BooleanFieldType},
582 }},
583 {Name: "rnested", Type: RecordFieldType, Repeated: true, Schema: Schema{
584 {Name: "b", Type: BooleanFieldType},
585 }},
586 {Name: "p", Type: IntegerFieldType, Required: false},
587 {Name: "n", Type: NumericFieldType, Required: false},
588 {Name: "nr", Type: NumericFieldType, Repeated: true},
589 {Name: "bn", Type: BigNumericFieldType, Required: false},
590 {Name: "bnr", Type: BigNumericFieldType, Repeated: true},
591 {Name: "g", Type: GeographyFieldType, Required: false},
592 {Name: "gr", Type: GeographyFieldType, Repeated: true},
593 }
594
595 type (
596 N struct{ B bool }
597 T struct {
598 S string
599 R []int
600 T civil.Time
601 TR []civil.Time
602 Nested *N
603 Rnested []*N
604 P NullInt64
605 N *big.Rat
606 NR []*big.Rat
607 BN *big.Rat
608 BNR []*big.Rat
609 G NullGeography
610 GR []string
611 }
612 )
613
614 check := func(msg string, in interface{}, want map[string]Value) {
615 ss := StructSaver{
616 Schema: schema,
617 InsertID: "iid",
618 Struct: in,
619 }
620 got, gotIID, err := ss.Save()
621 if err != nil {
622 t.Fatalf("%s: %v", msg, err)
623 }
624 if wantIID := "iid"; gotIID != wantIID {
625 t.Errorf("%s: InsertID: got %q, want %q", msg, gotIID, wantIID)
626 }
627 if diff := testutil.Diff(got, want); diff != "" {
628 t.Errorf("%s: %s", msg, diff)
629 }
630 }
631
632 ct1 := civil.Time{Hour: 1, Minute: 2, Second: 3, Nanosecond: 4000}
633 ct2 := civil.Time{Hour: 5, Minute: 6, Second: 7, Nanosecond: 8000}
634 in := T{
635 S: "x",
636 R: []int{1, 2},
637 T: ct1,
638 TR: []civil.Time{ct1, ct2},
639 Nested: &N{B: true},
640 Rnested: []*N{{true}, {false}},
641 P: NullInt64{Valid: true, Int64: 17},
642 N: big.NewRat(123456, 1000),
643 NR: []*big.Rat{big.NewRat(3, 1), big.NewRat(56789, 1e5)},
644 BN: big.NewRat(1, 3),
645 BNR: []*big.Rat{big.NewRat(1, 3), big.NewRat(1, 2)},
646 G: NullGeography{Valid: true, GeographyVal: "POINT(-122.350220 47.649154)"},
647 GR: []string{"POINT(-122.350220 47.649154)", "POINT(-122.198939 47.669865)"},
648 }
649 want := map[string]Value{
650 "s": "x",
651 "r": []int{1, 2},
652 "t": "01:02:03.000004",
653 "tr": []string{"01:02:03.000004", "05:06:07.000008"},
654 "nested": map[string]Value{"b": true},
655 "rnested": []Value{map[string]Value{"b": true}, map[string]Value{"b": false}},
656 "p": NullInt64{Valid: true, Int64: 17},
657 "n": "123.456000000",
658 "nr": []string{"3.000000000", "0.567890000"},
659 "bn": "0.33333333333333333333333333333333333333",
660 "bnr": []string{"0.33333333333333333333333333333333333333", "0.50000000000000000000000000000000000000"},
661 "g": NullGeography{Valid: true, GeographyVal: "POINT(-122.350220 47.649154)"},
662 "gr": []string{"POINT(-122.350220 47.649154)", "POINT(-122.198939 47.669865)"},
663 }
664 check("all values", in, want)
665 check("all values, ptr", &in, want)
666 check("empty struct", T{}, map[string]Value{"s": "", "t": "00:00:00", "p": NullInt64{}, "g": NullGeography{}})
667
668
669 type T2 struct {
670 S string
671
672 Extra int
673 }
674 check("missing and extra", T2{S: "x"}, map[string]Value{"s": "x"})
675
676 check("nils in slice", T{Rnested: []*N{{true}, nil, {false}}},
677 map[string]Value{
678 "s": "",
679 "t": "00:00:00",
680 "p": NullInt64{},
681 "g": NullGeography{},
682 "rnested": []Value{map[string]Value{"b": true}, map[string]Value(nil), map[string]Value{"b": false}},
683 })
684
685 check("zero-length repeated", T{Rnested: []*N{}},
686 map[string]Value{
687 "rnested": []Value{},
688 "s": "",
689 "t": "00:00:00",
690 "p": NullInt64{},
691 "g": NullGeography{},
692 })
693 }
694
695 func TestStructSaverErrors(t *testing.T) {
696 type (
697 badField struct {
698 I int `bigquery:"@"`
699 }
700 badR struct{ R int }
701 badRN struct{ R []int }
702 )
703
704 for i, test := range []struct {
705 inputStruct interface{}
706 schema Schema
707 }{
708 {0, nil},
709 {&badField{}, nil},
710 {&badR{}, Schema{{Name: "r", Repeated: true}}},
711 {&badR{}, Schema{{Name: "r", Type: RecordFieldType}}},
712 {&badRN{[]int{0}},
713 Schema{{Name: "r", Type: RecordFieldType, Repeated: true}}},
714 } {
715 ss := &StructSaver{Struct: test.inputStruct, Schema: test.schema}
716 _, _, err := ss.Save()
717 if err == nil {
718 t.Errorf("#%d, %v, %v: got nil, want error", i, test.inputStruct, test.schema)
719 }
720 }
721 }
722
723 func TestNumericStrings(t *testing.T) {
724 for _, tc := range []struct {
725 description string
726 in *big.Rat
727 wantNumeric string
728 wantBigNumeric string
729 }{
730 {"repeating with rounding", big.NewRat(2, 3), "0.666666667", "0.66666666666666666666666666666666666667"},
731 {"all zero padding", big.NewRat(1, 2), "0.500000000", "0.50000000000000000000000000000000000000"},
732 {"zero pad with digit", big.NewRat(1, 2*1e8), "0.000000005", "0.00000000500000000000000000000000000000"},
733 {"smaller rounding case 1", big.NewRat(5, 1e10), "0.000000001", "0.00000000050000000000000000000000000000"},
734 {"smaller rounding case 2", big.NewRat(-5, 1e10), "-0.000000001", "-0.00000000050000000000000000000000000000"},
735 } {
736 t.Run(tc.description, func(t *testing.T) {
737 if got := NumericString(tc.in); got != tc.wantNumeric {
738 t.Errorf("case %q, val %v as numeric: got %q, want %q", tc.description, tc.in, got, tc.wantNumeric)
739 }
740 if got := BigNumericString(tc.in); got != tc.wantBigNumeric {
741 t.Errorf("case %q, val %v as bignumeric: got %q, want %q", tc.description, tc.in, got, tc.wantBigNumeric)
742 }
743 })
744 }
745 }
746
747 func TestConvertRows(t *testing.T) {
748 schema := Schema{
749 {Type: StringFieldType},
750 {Type: IntegerFieldType},
751 {Type: FloatFieldType},
752 {Type: BooleanFieldType},
753 {Type: GeographyFieldType},
754 }
755 rows := []*bq.TableRow{
756 {F: []*bq.TableCell{
757 {V: "a"},
758 {V: "1"},
759 {V: "1.2"},
760 {V: "true"},
761 {V: "POINT(-122.350220 47.649154)"},
762 }},
763 {F: []*bq.TableCell{
764 {V: "b"},
765 {V: "2"},
766 {V: "2.2"},
767 {V: "false"},
768 {V: "POINT(-122.198939 47.669865)"},
769 }},
770 }
771 want := [][]Value{
772 {"a", int64(1), 1.2, true, "POINT(-122.350220 47.649154)"},
773 {"b", int64(2), 2.2, false, "POINT(-122.198939 47.669865)"},
774 }
775 got, err := convertRows(rows, schema)
776 if err != nil {
777 t.Fatalf("got %v, want nil", err)
778 }
779 if !testutil.Equal(got, want) {
780 t.Errorf("\ngot %v\nwant %v", got, want)
781 }
782
783 rows[0].F[0].V = 1
784 _, err = convertRows(rows, schema)
785 if err == nil {
786 t.Error("got nil, want error")
787 }
788 }
789
790 func TestValueList(t *testing.T) {
791 schema := Schema{
792 {Name: "s", Type: StringFieldType},
793 {Name: "i", Type: IntegerFieldType},
794 {Name: "f", Type: FloatFieldType},
795 {Name: "b", Type: BooleanFieldType},
796 }
797 want := []Value{"x", 7, 3.14, true}
798 var got []Value
799 vl := (*valueList)(&got)
800 if err := vl.Load(want, schema); err != nil {
801 t.Fatal(err)
802 }
803
804 if !testutil.Equal(got, want) {
805 t.Errorf("got %+v, want %+v", got, want)
806 }
807
808
809
810 if err := vl.Load(want, schema); err != nil {
811 t.Fatal(err)
812 }
813 if !testutil.Equal(got, want) {
814 t.Errorf("got %+v, want %+v", got, want)
815 }
816 }
817
818 func TestValueMap(t *testing.T) {
819 ns := Schema{
820 {Name: "x", Type: IntegerFieldType},
821 {Name: "y", Type: IntegerFieldType},
822 }
823 schema := Schema{
824 {Name: "s", Type: StringFieldType},
825 {Name: "i", Type: IntegerFieldType},
826 {Name: "f", Type: FloatFieldType},
827 {Name: "b", Type: BooleanFieldType},
828 {Name: "n", Type: RecordFieldType, Schema: ns},
829 {Name: "rn", Type: RecordFieldType, Schema: ns, Repeated: true},
830 }
831 in := []Value{"x", 7, 3.14, true,
832 []Value{1, 2},
833 []Value{[]Value{3, 4}, []Value{5, 6}},
834 }
835 var vm valueMap
836 if err := vm.Load(in, schema); err != nil {
837 t.Fatal(err)
838 }
839 want := map[string]Value{
840 "s": "x",
841 "i": 7,
842 "f": 3.14,
843 "b": true,
844 "n": map[string]Value{"x": 1, "y": 2},
845 "rn": []Value{
846 map[string]Value{"x": 3, "y": 4},
847 map[string]Value{"x": 5, "y": 6},
848 },
849 }
850 if !testutil.Equal(vm, valueMap(want)) {
851 t.Errorf("got\n%+v\nwant\n%+v", vm, want)
852 }
853
854 in = make([]Value, len(schema))
855 want = map[string]Value{
856 "s": nil,
857 "i": nil,
858 "f": nil,
859 "b": nil,
860 "n": nil,
861 "rn": nil,
862 }
863 var vm2 valueMap
864 if err := vm2.Load(in, schema); err != nil {
865 t.Fatal(err)
866 }
867 if !testutil.Equal(vm2, valueMap(want)) {
868 t.Errorf("got\n%+v\nwant\n%+v", vm2, want)
869 }
870 }
871
872 var (
873
874 schema2 = Schema{
875 {Name: "s", Type: StringFieldType},
876 {Name: "s2", Type: StringFieldType},
877 {Name: "by", Type: BytesFieldType},
878 {Name: "I", Type: IntegerFieldType},
879 {Name: "U", Type: IntegerFieldType},
880 {Name: "F", Type: FloatFieldType},
881 {Name: "B", Type: BooleanFieldType},
882 {Name: "TS", Type: TimestampFieldType},
883 {Name: "D", Type: DateFieldType},
884 {Name: "T", Type: TimeFieldType},
885 {Name: "DT", Type: DateTimeFieldType},
886 {Name: "N", Type: NumericFieldType},
887 {Name: "BN", Type: BigNumericFieldType},
888 {Name: "G", Type: GeographyFieldType},
889 {Name: "nested", Type: RecordFieldType, Schema: Schema{
890 {Name: "nestS", Type: StringFieldType},
891 {Name: "nestI", Type: IntegerFieldType},
892 }},
893 {Name: "t", Type: StringFieldType},
894 }
895
896 testTimestamp = time.Date(2016, 11, 5, 7, 50, 22, 8, time.UTC)
897 testDate = civil.Date{Year: 2016, Month: 11, Day: 5}
898 testTime = civil.Time{Hour: 7, Minute: 50, Second: 22, Nanosecond: 8}
899 testDateTime = civil.DateTime{Date: testDate, Time: testTime}
900 testNumeric = big.NewRat(123, 456)
901 testBigNumeric = big.NewRat(456, 789)
902
903 testGeography = "POINT(-122.350220 47.649154)"
904
905 testValues = []Value{"x", "y", []byte{1, 2, 3}, int64(7), int64(8), 3.14, true,
906 testTimestamp, testDate, testTime, testDateTime, testNumeric, testBigNumeric, testGeography,
907 []Value{"nested", int64(17)}, "z"}
908 )
909
910 type testStruct1 struct {
911 B bool
912 I int
913 U uint16
914 times
915 S string
916 S2 String
917 By []byte
918 F float64
919 N *big.Rat
920 BN *big.Rat
921 G string
922 Nested nested
923 Tagged string `bigquery:"t"`
924 }
925
926 type String string
927
928 type nested struct {
929 NestS string
930 NestI int
931 }
932
933 type times struct {
934 TS time.Time
935 T civil.Time
936 D civil.Date
937 DT civil.DateTime
938 }
939
940 func TestStructLoader(t *testing.T) {
941 var ts1 testStruct1
942 mustLoad(t, &ts1, schema2, testValues)
943
944
945 want := &testStruct1{
946 B: true,
947 I: 7,
948 U: 8,
949 F: 3.14,
950 times: times{TS: testTimestamp, T: testTime, D: testDate, DT: testDateTime},
951 S: "x",
952 S2: "y",
953 By: []byte{1, 2, 3},
954 N: big.NewRat(123, 456),
955 BN: big.NewRat(456, 789),
956 G: testGeography,
957 Nested: nested{NestS: "nested", NestI: 17},
958 Tagged: "z",
959 }
960 if diff := testutil.Diff(&ts1, want, cmp.AllowUnexported(testStruct1{})); diff != "" {
961 t.Error(diff)
962 }
963
964
965 type nestedPtr struct{ Nested *nested }
966 var np nestedPtr
967 mustLoad(t, &np, schema2, testValues)
968 want2 := &nestedPtr{Nested: &nested{NestS: "nested", NestI: 17}}
969 if diff := testutil.Diff(&np, want2); diff != "" {
970 t.Error(diff)
971 }
972
973
974 nst := &nested{NestS: "x", NestI: -10}
975 np = nestedPtr{Nested: nst}
976 mustLoad(t, &np, schema2, testValues)
977 if diff := testutil.Diff(&np, want2); diff != "" {
978 t.Error(diff)
979 }
980 if np.Nested != nst {
981 t.Error("nested struct pointers not equal")
982 }
983 }
984
985 type repStruct struct {
986 Nums []int
987 ShortNums [2]int
988 LongNums [5]int
989 Nested []*nested
990 }
991
992 var (
993 repSchema = Schema{
994 {Name: "nums", Type: IntegerFieldType, Repeated: true},
995 {Name: "shortNums", Type: IntegerFieldType, Repeated: true},
996 {Name: "longNums", Type: IntegerFieldType, Repeated: true},
997 {Name: "nested", Type: RecordFieldType, Repeated: true, Schema: Schema{
998 {Name: "nestS", Type: StringFieldType},
999 {Name: "nestI", Type: IntegerFieldType},
1000 }},
1001 }
1002 v123 = []Value{int64(1), int64(2), int64(3)}
1003 repValues = []Value{v123, v123, v123,
1004 []Value{
1005 []Value{"x", int64(1)},
1006 []Value{"y", int64(2)},
1007 },
1008 }
1009 )
1010
1011 func TestStructLoaderRepeated(t *testing.T) {
1012 var r1 repStruct
1013 mustLoad(t, &r1, repSchema, repValues)
1014 want := repStruct{
1015 Nums: []int{1, 2, 3},
1016 ShortNums: [...]int{1, 2},
1017 LongNums: [...]int{1, 2, 3, 0, 0},
1018 Nested: []*nested{{"x", 1}, {"y", 2}},
1019 }
1020 if diff := testutil.Diff(r1, want); diff != "" {
1021 t.Error(diff)
1022 }
1023 r2 := repStruct{
1024 Nums: []int{-1, -2, -3, -4, -5},
1025 LongNums: [...]int{-1, -2, -3, -4, -5},
1026 }
1027 mustLoad(t, &r2, repSchema, repValues)
1028 if diff := testutil.Diff(r2, want); diff != "" {
1029 t.Error(diff)
1030 }
1031 if got, want := cap(r2.Nums), 5; got != want {
1032 t.Errorf("cap(r2.Nums) = %d, want %d", got, want)
1033 }
1034
1035
1036 r3 := repStruct{Nums: []int{-1}}
1037 mustLoad(t, &r3, repSchema, repValues)
1038 if diff := testutil.Diff(r3, want); diff != "" {
1039 t.Error(diff)
1040 }
1041 if got, want := cap(r3.Nums), 3; got != want {
1042 t.Errorf("cap(r3.Nums) = %d, want %d", got, want)
1043 }
1044 }
1045
1046 type testStructNullable struct {
1047 String NullString
1048 Bytes []byte
1049 Integer NullInt64
1050 Float NullFloat64
1051 Boolean NullBool
1052 Timestamp NullTimestamp
1053 Date NullDate
1054 Time NullTime
1055 DateTime NullDateTime
1056 Numeric *big.Rat
1057 BigNumeric *big.Rat
1058 Geography NullGeography
1059 Record *subNullable
1060 }
1061
1062 type subNullable struct {
1063 X NullInt64
1064 }
1065
1066 var testStructNullableSchema = Schema{
1067 {Name: "String", Type: StringFieldType, Required: false},
1068 {Name: "Bytes", Type: BytesFieldType, Required: false},
1069 {Name: "Integer", Type: IntegerFieldType, Required: false},
1070 {Name: "Float", Type: FloatFieldType, Required: false},
1071 {Name: "Boolean", Type: BooleanFieldType, Required: false},
1072 {Name: "Timestamp", Type: TimestampFieldType, Required: false},
1073 {Name: "Date", Type: DateFieldType, Required: false},
1074 {Name: "Time", Type: TimeFieldType, Required: false},
1075 {Name: "DateTime", Type: DateTimeFieldType, Required: false},
1076 {Name: "Numeric", Type: NumericFieldType, Required: false},
1077 {Name: "BigNumeric", Type: BigNumericFieldType, Required: false},
1078 {Name: "Geography", Type: GeographyFieldType, Required: false},
1079 {Name: "Record", Type: RecordFieldType, Required: false, Schema: Schema{
1080 {Name: "X", Type: IntegerFieldType, Required: false},
1081 }},
1082 }
1083
1084 func TestStructLoaderNullable(t *testing.T) {
1085 var ts testStructNullable
1086 nilVals := make([]Value, len(testStructNullableSchema))
1087 mustLoad(t, &ts, testStructNullableSchema, nilVals)
1088 want := testStructNullable{}
1089 if diff := testutil.Diff(ts, want); diff != "" {
1090 t.Error(diff)
1091 }
1092
1093 nonnilVals := []Value{"x", []byte{1, 2, 3}, int64(1), 2.3, true, testTimestamp, testDate, testTime,
1094 testDateTime, big.NewRat(1, 2), big.NewRat(3, 4), testGeography, []Value{int64(4)}}
1095
1096
1097
1098 mustLoad(t, &ts, testStructNullableSchema, nonnilVals)
1099 want = testStructNullable{
1100 String: NullString{StringVal: "x", Valid: true},
1101 Bytes: []byte{1, 2, 3},
1102 Integer: NullInt64{Int64: 1, Valid: true},
1103 Float: NullFloat64{Float64: 2.3, Valid: true},
1104 Boolean: NullBool{Bool: true, Valid: true},
1105 Timestamp: NullTimestamp{Timestamp: testTimestamp, Valid: true},
1106 Date: NullDate{Date: testDate, Valid: true},
1107 Time: NullTime{Time: testTime, Valid: true},
1108 DateTime: NullDateTime{DateTime: testDateTime, Valid: true},
1109 Numeric: big.NewRat(1, 2),
1110 BigNumeric: big.NewRat(3, 4),
1111 Geography: NullGeography{GeographyVal: testGeography, Valid: true},
1112 Record: &subNullable{X: NullInt64{Int64: 4, Valid: true}},
1113 }
1114 if diff := testutil.Diff(ts, want); diff != "" {
1115 t.Error(diff)
1116 }
1117
1118
1119 want = ts
1120 want.Bytes = []byte{17}
1121 vals2 := []Value{nil, []byte{17}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []Value{int64(7)}}
1122 mustLoad(t, &ts, testStructNullableSchema, vals2)
1123 if ts.Record != want.Record {
1124 t.Error("record pointers not identical")
1125 }
1126 }
1127
1128 func TestStructLoaderOverflow(t *testing.T) {
1129 type S struct {
1130 I int16
1131 U uint16
1132 F float32
1133 }
1134 schema := Schema{
1135 {Name: "I", Type: IntegerFieldType},
1136 {Name: "U", Type: IntegerFieldType},
1137 {Name: "F", Type: FloatFieldType},
1138 }
1139 var s S
1140 z64 := int64(0)
1141 for _, vals := range [][]Value{
1142 {int64(math.MaxInt16 + 1), z64, 0},
1143 {z64, int64(math.MaxInt32), 0},
1144 {z64, int64(-1), 0},
1145 {z64, z64, math.MaxFloat32 * 2},
1146 } {
1147 if err := load(&s, schema, vals); err == nil {
1148 t.Errorf("%+v: got nil, want error", vals)
1149 }
1150 }
1151 }
1152
1153 func TestStructLoaderFieldOverlap(t *testing.T) {
1154
1155 type S1 struct {
1156 I int
1157 X [][]int
1158
1159 }
1160 var s1 S1
1161 if err := load(&s1, schema2, testValues); err != nil {
1162 t.Fatal(err)
1163 }
1164 want1 := S1{I: 7}
1165 if diff := testutil.Diff(s1, want1); diff != "" {
1166 t.Error(diff)
1167 }
1168
1169
1170 type S2 struct{ Z int }
1171
1172 var s2 S2
1173 mustLoad(t, &s2, schema2, testValues)
1174 want2 := S2{}
1175 if diff := testutil.Diff(s2, want2); diff != "" {
1176 t.Error(diff)
1177 }
1178 }
1179
1180 func TestStructLoaderErrors(t *testing.T) {
1181 check := func(sp interface{}) {
1182 var sl structLoader
1183 err := sl.set(sp, schema2)
1184 if err == nil {
1185 t.Errorf("%T: got nil, want error", sp)
1186 }
1187 }
1188
1189 type bad1 struct{ F int32 }
1190 check(&bad1{})
1191
1192 type bad2 struct{ I uint }
1193 check(&bad2{})
1194
1195 type bad3 struct {
1196 I int `bigquery:"@"`
1197 }
1198 check(&bad3{})
1199
1200 type bad4 struct{ Nested int }
1201 check(&bad4{})
1202
1203 type bad5 struct{ Nested struct{ NestS int } }
1204 check(&bad5{})
1205
1206 bad6 := &struct{ Nums int }{}
1207 sl := structLoader{}
1208 err := sl.set(bad6, repSchema)
1209 if err == nil {
1210 t.Errorf("%T: got nil, want error", bad6)
1211 }
1212
1213
1214 err2 := sl.set(&repStruct{}, repSchema)
1215 if err2 != err {
1216 t.Errorf("%v != %v, expected equal", err2, err)
1217 }
1218
1219 err2 = sl.Load(nil, nil)
1220 if err2 != err {
1221 t.Errorf("%v != %v, expected equal", err2, err)
1222 }
1223
1224
1225 schema := Schema{
1226 {Name: "i", Type: IntegerFieldType},
1227 {Name: "f", Type: FloatFieldType},
1228 {Name: "b", Type: BooleanFieldType},
1229 {Name: "s", Type: StringFieldType},
1230 {Name: "d", Type: DateFieldType},
1231 {Name: "r", Type: RecordFieldType, Schema: Schema{{Name: "X", Type: IntegerFieldType}}},
1232 }
1233 type s struct {
1234 I int
1235 F float64
1236 B bool
1237 S string
1238 D civil.Date
1239 }
1240 vstruct := reflect.ValueOf(s{}).Type()
1241 fieldNames := []string{"I", "F", "B", "S", "D"}
1242 vals := []Value{int64(0), 0.0, false, "", testDate}
1243 mustLoad(t, &s{}, schema, vals)
1244 for i, e := range vals {
1245 vals[i] = nil
1246 got := load(&s{}, schema, vals)
1247 if errors.Is(got, errNoNulls) {
1248 t.Errorf("#%d: got %v, want %v", i, got, errNoNulls)
1249 }
1250 f, _ := vstruct.FieldByName(fieldNames[i])
1251 expectedError := fmt.Sprintf("bigquery: NULL cannot be assigned to field `%s` of type %s", f.Name, f.Type.Name())
1252 if got.Error() != expectedError {
1253 t.Errorf("#%d: got %v, want %v", i, got, expectedError)
1254 }
1255 vals[i] = e
1256 }
1257
1258
1259 type different struct {
1260 B bool
1261 I int
1262 times
1263 S string
1264 Nums []int
1265 }
1266
1267 sl = structLoader{}
1268 if err := sl.set(&testStruct1{}, schema2); err != nil {
1269 t.Fatal(err)
1270 }
1271 err = sl.set(&different{}, schema2)
1272 if err == nil {
1273 t.Error("different struct types: got nil, want error")
1274 }
1275 }
1276
1277 func mustLoad(t *testing.T, pval interface{}, schema Schema, vals []Value) {
1278 if err := load(pval, schema, vals); err != nil {
1279 t.Fatalf("loading: %v", err)
1280 }
1281 }
1282
1283 func load(pval interface{}, schema Schema, vals []Value) error {
1284 var sl structLoader
1285 if err := sl.set(pval, schema); err != nil {
1286 return err
1287 }
1288 return sl.Load(vals, nil)
1289 }
1290
1291 func BenchmarkStructLoader_NoCompile(b *testing.B) {
1292 benchmarkStructLoader(b, false)
1293 }
1294
1295 func BenchmarkStructLoader_Compile(b *testing.B) {
1296 benchmarkStructLoader(b, true)
1297 }
1298
1299 func benchmarkStructLoader(b *testing.B, compile bool) {
1300 var ts1 testStruct1
1301 for i := 0; i < b.N; i++ {
1302 var sl structLoader
1303 for j := 0; j < 10; j++ {
1304 if err := load(&ts1, schema2, testValues); err != nil {
1305 b.Fatal(err)
1306 }
1307 if !compile {
1308 sl.typ = nil
1309 }
1310 }
1311 }
1312 }
1313
View as plain text