1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package bigquery
16
17 import (
18 "context"
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 var scalarTests = []struct {
34 name string
35 val interface{}
36 wantNil bool
37 wantVal string
38 wantType *bq.QueryParameterType
39 wantStat interface{}
40 }{
41 {"Int64Default", int64(0), false, "0", int64ParamType, int64(0)},
42 {"NullInt64Valued", NullInt64{Int64: 3, Valid: true}, false, "3", int64ParamType, int64(3)},
43 {"NullInt64Null", NullInt64{Valid: false}, true, "", int64ParamType, NullInt64{Valid: false}},
44 {"FloatLiteral", 3.14, false, "3.14", float64ParamType, 3.14},
45 {"FloatLiteralExponent", 3.14159e-87, false, "3.14159e-87", float64ParamType, 3.14159e-87},
46 {"NullFloatValued", NullFloat64{Float64: 3.14, Valid: true}, false, "3.14", float64ParamType, 3.14},
47 {"NullFloatNull", NullFloat64{Valid: false}, true, "", float64ParamType, NullFloat64{Valid: false}},
48 {"FloatNaN", math.NaN(), false, "NaN", float64ParamType, math.NaN()},
49 {"Boolean", true, false, "true", boolParamType, true},
50 {"NullBoolValued", NullBool{Bool: true, Valid: true}, false, "true", boolParamType, true},
51 {"NullBoolNull", NullBool{Valid: false}, true, "", boolParamType, NullBool{Valid: false}},
52 {"String", "string", false, "string", stringParamType, "string"},
53 {"StringUnicode", "\u65e5\u672c\u8a9e\n", false, "\u65e5\u672c\u8a9e\n", stringParamType, "\u65e5\u672c\u8a9e\n"},
54 {"NullStringValued", NullString{StringVal: "string2", Valid: true}, false, "string2", stringParamType, "string2"},
55 {"NullStringNull", NullString{Valid: false}, true, "", stringParamType, NullString{Valid: false}},
56 {"Bytes", []byte("foo"), false, "Zm9v", bytesParamType, []byte("foo")},
57 {"TimestampFixed", time.Date(2016, 3, 20, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720)),
58 false,
59 "2016-03-20 04:22:09.000005-01:02",
60 timestampParamType,
61 time.Date(2016, 3, 20, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720))},
62 {"NullTimestampValued", NullTimestamp{Timestamp: time.Date(2016, 3, 22, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720)), Valid: true},
63 false,
64 "2016-03-22 04:22:09.000005-01:02",
65 timestampParamType,
66 time.Date(2016, 3, 22, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720))},
67 {"NullTimestampNull", NullTimestamp{Valid: false},
68 true,
69 "",
70 timestampParamType,
71 NullTimestamp{Valid: false}},
72 {"Date", civil.Date{Year: 2016, Month: 3, Day: 20},
73 false,
74 "2016-03-20",
75 dateParamType,
76 civil.Date{Year: 2016, Month: 3, Day: 20}},
77 {"NullDateValued", NullDate{
78 Date: civil.Date{Year: 2016, Month: 3, Day: 24}, Valid: true},
79 false,
80 "2016-03-24",
81 dateParamType,
82 civil.Date{Year: 2016, Month: 3, Day: 24}},
83 {"NullDateNull", NullDate{Valid: false},
84 true,
85 "",
86 dateParamType,
87 NullDate{Valid: false}},
88 {"Time", civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000},
89 false,
90 "04:05:06.789000",
91 timeParamType,
92 civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}},
93 {"NullTimeValued", NullTime{
94 Time: civil.Time{Hour: 6, Minute: 7, Second: 8, Nanosecond: 789000000}, Valid: true},
95 false,
96 "06:07:08.789000",
97 timeParamType,
98 civil.Time{Hour: 6, Minute: 7, Second: 8, Nanosecond: 789000000}},
99 {"NullTimeNull", NullTime{Valid: false},
100 true,
101 "",
102 timeParamType,
103 NullTime{Valid: false}},
104 {"Datetime", civil.DateTime{Date: civil.Date{Year: 2016, Month: 3, Day: 20}, Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}},
105 false,
106 "2016-03-20 04:05:06.789000",
107 dateTimeParamType,
108 civil.DateTime{Date: civil.Date{Year: 2016, Month: 3, Day: 20}, Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}}},
109 {"NullDateTimeValued", NullDateTime{
110 DateTime: civil.DateTime{Date: civil.Date{Year: 2016, Month: 3, Day: 21}, Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}}, Valid: true},
111 false,
112 "2016-03-21 04:05:06.789000",
113 dateTimeParamType,
114 civil.DateTime{Date: civil.Date{Year: 2016, Month: 3, Day: 21}, Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000}}},
115 {"NullDateTimeNull", NullDateTime{Valid: false},
116 true,
117 "",
118 dateTimeParamType,
119 NullDateTime{Valid: false}},
120 {"Numeric", big.NewRat(12345, 1000), false, "12.345000000", numericParamType, big.NewRat(12345, 1000)},
121 {"BignumericParam", &QueryParameterValue{
122 Type: StandardSQLDataType{
123 TypeKind: "BIGNUMERIC",
124 },
125 Value: BigNumericString(big.NewRat(12345, 10e10)),
126 }, false, "0.00000012345000000000000000000000000000", bigNumericParamType, big.NewRat(12345, 10e10)},
127 {"IntervalValue", &IntervalValue{Years: 1, Months: 2, Days: 3}, false, "1-2 3 0:0:0", intervalParamType, &IntervalValue{Years: 1, Months: 2, Days: 3}},
128 {"NullGeographyValued", NullGeography{GeographyVal: "POINT(-122.335503 47.625536)", Valid: true}, false, "POINT(-122.335503 47.625536)", geographyParamType, "POINT(-122.335503 47.625536)"},
129 {"NullGeographyNull", NullGeography{Valid: false}, true, "", geographyParamType, NullGeography{Valid: false}},
130 {"NullJsonValued", NullJSON{Valid: true, JSONVal: "{\"alpha\":\"beta\"}"}, false, "{\"alpha\":\"beta\"}", jsonParamType, "{\"alpha\":\"beta\"}"},
131 {"NullJsonNull", NullJSON{Valid: false}, true, "", jsonParamType, NullJSON{Valid: false}},
132 }
133
134 type (
135 S1 struct {
136 A int
137 B *S2
138 C bool
139 }
140 S2 struct {
141 D string
142 }
143 )
144
145 var (
146 s1 = S1{
147 A: 1,
148 B: &S2{D: "s"},
149 C: true,
150 }
151
152 s1ParamType = &bq.QueryParameterType{
153 Type: "STRUCT",
154 StructTypes: []*bq.QueryParameterTypeStructTypes{
155 {Name: "A", Type: int64ParamType},
156 {Name: "B", Type: &bq.QueryParameterType{
157 Type: "STRUCT",
158 StructTypes: []*bq.QueryParameterTypeStructTypes{
159 {Name: "D", Type: stringParamType},
160 },
161 }},
162 {Name: "C", Type: boolParamType},
163 },
164 }
165
166 s1ParamValue = bq.QueryParameterValue{
167 StructValues: map[string]bq.QueryParameterValue{
168 "A": sval("1"),
169 "B": {
170 StructValues: map[string]bq.QueryParameterValue{
171 "D": sval("s"),
172 },
173 },
174 "C": sval("true"),
175 },
176 }
177
178 s1ParamReturnValue = map[string]interface{}{
179 "A": int64(1),
180 "B": map[string]interface{}{"D": "s"},
181 "C": true,
182 }
183 )
184
185 func sval(s string) bq.QueryParameterValue {
186 return bq.QueryParameterValue{Value: s}
187 }
188
189 func TestParamValueScalar(t *testing.T) {
190 nilValue := &bq.QueryParameterValue{
191 NullFields: []string{"Value"},
192 }
193
194 for _, tc := range scalarTests {
195 t.Run(tc.name, func(t *testing.T) {
196 got, err := paramValue(reflect.ValueOf(tc.val))
197 if err != nil {
198 t.Errorf("%v: got err %v", tc.val, err)
199 }
200 if tc.wantNil {
201 if !testutil.Equal(got, nilValue) {
202 t.Errorf("%#v: wanted empty QueryParameterValue, got %v", tc.val, got)
203 }
204 } else {
205 want := sval(tc.wantVal)
206 if !testutil.Equal(got, &want) {
207 t.Errorf("%#v:\ngot %+v\nwant %+v", tc.val, got, want)
208 }
209 }
210 })
211 }
212 }
213
214 func TestParamValueRange(t *testing.T) {
215
216 tTimestamp := time.Date(2016, 3, 22, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720))
217 tDate := civil.Date{Year: 2016, Month: 03, Day: 22}
218 tDateTime := civil.DateTime{
219 Date: civil.Date{Year: 2017, Month: 7, Day: 13},
220 Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 789000000},
221 }
222 wTimestamp := "2016-03-22 04:22:09.000005-01:02"
223 wDate := "2016-03-22"
224 wDateTime := "2017-07-13 04:05:06.789000"
225
226 var testCases = []struct {
227 desc string
228 in interface{}
229 want *bq.QueryParameterValue
230 }{
231 {
232 desc: "RangeValue time.Time both populated",
233 in: &RangeValue{
234 Start: tTimestamp,
235 End: tTimestamp,
236 },
237 want: &bq.QueryParameterValue{
238 RangeValue: &bq.RangeValue{
239 Start: &bq.QueryParameterValue{
240 Value: wTimestamp,
241 },
242 End: &bq.QueryParameterValue{
243 Value: wTimestamp,
244 },
245 },
246 },
247 },
248 {
249 desc: "RangeValue time.Time start only",
250 in: &RangeValue{
251 Start: tTimestamp,
252 },
253 want: &bq.QueryParameterValue{
254 RangeValue: &bq.RangeValue{
255 Start: &bq.QueryParameterValue{
256 Value: wTimestamp,
257 },
258 },
259 },
260 },
261 {
262 desc: "RangeValue time.Time end only",
263 in: &RangeValue{
264 End: tTimestamp,
265 },
266 want: &bq.QueryParameterValue{
267 RangeValue: &bq.RangeValue{
268 End: &bq.QueryParameterValue{
269 Value: wTimestamp,
270 },
271 },
272 },
273 },
274 {
275 desc: "RangeValue NullTimestamp both populated",
276 in: &RangeValue{
277 Start: NullTimestamp{Valid: true, Timestamp: tTimestamp},
278 End: NullTimestamp{Valid: true, Timestamp: tTimestamp},
279 },
280 want: &bq.QueryParameterValue{
281 RangeValue: &bq.RangeValue{
282 Start: &bq.QueryParameterValue{
283 Value: wTimestamp,
284 },
285 End: &bq.QueryParameterValue{
286 Value: wTimestamp,
287 },
288 },
289 },
290 },
291 {
292 desc: "RangeValue NullTimestamp start only",
293 in: &RangeValue{
294 Start: NullTimestamp{Valid: true, Timestamp: tTimestamp},
295 End: NullTimestamp{Valid: false},
296 },
297 want: &bq.QueryParameterValue{
298 RangeValue: &bq.RangeValue{
299 Start: &bq.QueryParameterValue{
300 Value: wTimestamp,
301 },
302 End: &bq.QueryParameterValue{NullFields: []string{"Value"}},
303 },
304 },
305 },
306 {
307 desc: "RangeValue time.Time end only",
308 in: &RangeValue{
309 Start: NullTimestamp{Valid: false},
310 End: NullTimestamp{Valid: true, Timestamp: tTimestamp},
311 },
312 want: &bq.QueryParameterValue{
313 RangeValue: &bq.RangeValue{
314 Start: &bq.QueryParameterValue{NullFields: []string{"Value"}},
315 End: &bq.QueryParameterValue{
316 Value: wTimestamp,
317 },
318 },
319 },
320 },
321 {
322 desc: "RangeValue civil.Date both populated",
323 in: &RangeValue{
324 Start: tDate,
325 End: tDate,
326 },
327 want: &bq.QueryParameterValue{
328 RangeValue: &bq.RangeValue{
329 Start: &bq.QueryParameterValue{
330 Value: wDate,
331 },
332 End: &bq.QueryParameterValue{
333 Value: wDate,
334 },
335 },
336 },
337 },
338 {
339 desc: "RangeValue civil.DateTime both populated",
340 in: &RangeValue{
341 Start: tDateTime,
342 End: tDateTime,
343 },
344 want: &bq.QueryParameterValue{
345 RangeValue: &bq.RangeValue{
346 Start: &bq.QueryParameterValue{
347 Value: wDateTime,
348 },
349 End: &bq.QueryParameterValue{
350 Value: wDateTime,
351 },
352 },
353 },
354 },
355 {
356 desc: "Unbounded Range in QueryParameterValue",
357 in: &QueryParameterValue{
358 Type: StandardSQLDataType{
359 TypeKind: "RANGE",
360 RangeElementType: &StandardSQLDataType{
361 TypeKind: "DATETIME",
362 },
363 },
364 Value: &RangeValue{},
365 },
366 want: &bq.QueryParameterValue{
367 RangeValue: &bq.RangeValue{},
368 },
369 },
370 }
371
372 for _, tc := range testCases {
373 got, err := paramValue(reflect.ValueOf(tc.in))
374 if err != nil {
375 t.Errorf("%q: got error %v", tc.desc, err)
376 }
377 if d := testutil.Diff(got, tc.want); d != "" {
378 t.Errorf("%q: mismatch\n%s", tc.desc, d)
379 }
380 }
381 }
382
383 func TestParamValueArray(t *testing.T) {
384 qpv := &bq.QueryParameterValue{ArrayValues: []*bq.QueryParameterValue{
385 {Value: "1"},
386 {Value: "2"},
387 },
388 }
389 for _, tc := range []struct {
390 name string
391 val interface{}
392 want *bq.QueryParameterValue
393 }{
394 {"nilIntSlice", []int(nil), &bq.QueryParameterValue{}},
395 {"emptyIntSlice", []int{}, &bq.QueryParameterValue{}},
396 {"slice", []int{1, 2}, qpv},
397 {"array", [2]int{1, 2}, qpv},
398 } {
399 got, err := paramValue(reflect.ValueOf(tc.val))
400 if err != nil {
401 t.Fatal(err)
402 }
403 if !testutil.Equal(got, tc.want) {
404 t.Errorf("%#v:\ngot %+v\nwant %+v", tc.val, got, tc.want)
405 }
406 }
407 }
408
409 func TestParamValueStruct(t *testing.T) {
410 got, err := paramValue(reflect.ValueOf(s1))
411 if err != nil {
412 t.Fatal(err)
413 }
414 if !testutil.Equal(got, &s1ParamValue) {
415 t.Errorf("got %+v\nwant %+v", got, &s1ParamValue)
416 }
417 }
418
419 func TestParamValueErrors(t *testing.T) {
420
421
422 for _, val := range []interface{}{nil, new([]int)} {
423 _, err := paramValue(reflect.ValueOf(val))
424 if err == nil {
425 t.Errorf("%v (%T): got nil, want error", val, val)
426 }
427 }
428 }
429
430 func TestParamType(t *testing.T) {
431 for _, tc := range scalarTests {
432 t.Run(fmt.Sprintf("scalar-%s", tc.name), func(t *testing.T) {
433 got, err := paramType(reflect.TypeOf(tc.val), reflect.ValueOf(tc.val))
434 if err != nil {
435 t.Fatal(err)
436 }
437 if d := testutil.Diff(got, tc.wantType); d != "" {
438 t.Errorf("%v (%T): \n%s", tc.val, tc.val, d)
439 }
440 })
441 }
442 for _, tc := range []struct {
443 name string
444 val interface{}
445 want *bq.QueryParameterType
446 }{
447 {"uint32", uint32(32767), int64ParamType},
448 {"byteSlice", []byte("foo"), bytesParamType},
449 {"intArray", []int{}, &bq.QueryParameterType{Type: "ARRAY", ArrayType: int64ParamType}},
450 {"boolArray", [3]bool{}, &bq.QueryParameterType{Type: "ARRAY", ArrayType: boolParamType}},
451 {"emptyStruct", S1{}, s1ParamType},
452 {"RangeTimestampNilEnd", &RangeValue{Start: time.Now()}, &bq.QueryParameterType{Type: "RANGE", RangeElementType: timestampParamType}},
453 {"RangeTimestampNilStart", &RangeValue{End: time.Now()}, &bq.QueryParameterType{Type: "RANGE", RangeElementType: timestampParamType}},
454 {"RangeTimestampNullValStart", &RangeValue{Start: NullTimestamp{Valid: false}}, &bq.QueryParameterType{Type: "RANGE", RangeElementType: timestampParamType}},
455 {"RangeTimestampNullValEnd", &RangeValue{End: NullTimestamp{Valid: false}}, &bq.QueryParameterType{Type: "RANGE", RangeElementType: timestampParamType}},
456 {"RangeDateTimeEmptyStart", &RangeValue{Start: civil.DateTime{}}, &bq.QueryParameterType{Type: "RANGE", RangeElementType: dateTimeParamType}},
457 {"RangeDateEmptyEnd", &RangeValue{End: civil.Date{}}, &bq.QueryParameterType{Type: "RANGE", RangeElementType: dateParamType}},
458 {"RangeDateTimeInQPV",
459 &QueryParameterValue{
460 Type: StandardSQLDataType{
461 TypeKind: "RANGE",
462 RangeElementType: &StandardSQLDataType{
463 TypeKind: "DATETIME",
464 },
465 },
466 Value: &RangeValue{},
467 },
468 &bq.QueryParameterType{
469 Type: "RANGE",
470 RangeElementType: &bq.QueryParameterType{
471 Type: "DATETIME",
472 },
473 },
474 },
475 } {
476 t.Run(fmt.Sprintf("complex-%s", tc.name), func(t *testing.T) {
477 got, err := paramType(reflect.TypeOf(tc.val), reflect.ValueOf(tc.val))
478 if err != nil {
479 t.Fatal(err)
480 }
481 if d := testutil.Diff(got, tc.want); d != "" {
482 t.Errorf("%v (%T): \n%s", tc.val, tc.val, d)
483 }
484 })
485 }
486 }
487 func TestParamTypeErrors(t *testing.T) {
488 for _, val := range []interface{}{
489 nil, uint(0), new([]int), make(chan int), map[int]interface{}{}, &RangeValue{},
490 } {
491 _, err := paramType(reflect.TypeOf(val), reflect.ValueOf(val))
492 if err == nil {
493 t.Errorf("%v (%T): got nil, want error", val, val)
494 }
495 }
496
497 type recArr struct {
498 RecArr []recArr
499 }
500 type recMap struct {
501 RecMap map[string]recMap
502 }
503 queryParam := QueryParameterValue{
504 StructValue: map[string]QueryParameterValue{
505 "nested": {
506 Type: StandardSQLDataType{
507 TypeKind: "STRING",
508 },
509 Value: "TEST",
510 },
511 },
512 }
513 standardSQL := StandardSQLDataType{
514 ArrayElementType: &StandardSQLDataType{
515 TypeKind: "NUMERIC",
516 },
517 }
518 recursiveArr := recArr{
519 RecArr: []recArr{},
520 }
521 recursiveMap := recMap{
522 RecMap: map[string]recMap{},
523 }
524
525 for _, val := range []interface{}{
526 queryParam, standardSQL, recursiveArr, recursiveMap,
527 } {
528 _, err := paramType(reflect.TypeOf(val), reflect.ValueOf(val))
529 if err == nil {
530 t.Errorf("%v (%T): got nil, want error", val, val)
531 }
532 }
533 }
534
535 func TestConvertParamValue(t *testing.T) {
536
537 for _, tc := range scalarTests {
538 t.Run(fmt.Sprintf("scalar-%s", tc.name), func(t *testing.T) {
539 pval, err := paramValue(reflect.ValueOf(tc.val))
540 if err != nil {
541 t.Fatal(err)
542 }
543 ptype, err := paramType(reflect.TypeOf(tc.val), reflect.ValueOf(tc.val))
544 if err != nil {
545 t.Fatal(err)
546 }
547 got, err := convertParamValue(pval, ptype)
548 if err != nil {
549 t.Fatalf("convertParamValue(%+v, %+v): %v", pval, ptype, err)
550 }
551 if !testutil.Equal(got, tc.wantStat) {
552 t.Errorf("%#v: wanted stat as %#v, got %#v", tc.val, tc.wantStat, got)
553 }
554 })
555 }
556
557 for _, tc := range []struct {
558 name string
559 pval *bq.QueryParameterValue
560 want []interface{}
561 }{
562 {
563 "empty",
564 &bq.QueryParameterValue{},
565 nil,
566 },
567 {
568 "intArray",
569 &bq.QueryParameterValue{
570 ArrayValues: []*bq.QueryParameterValue{{Value: "1"}, {Value: "2"}},
571 },
572 []interface{}{int64(1), int64(2)},
573 },
574 } {
575 t.Run(fmt.Sprintf("array-%s", tc.name), func(t *testing.T) {
576 ptype := &bq.QueryParameterType{Type: "ARRAY", ArrayType: int64ParamType}
577 got, err := convertParamValue(tc.pval, ptype)
578 if err != nil {
579 t.Fatalf("%+v: %v", tc.pval, err)
580 }
581 if !testutil.Equal(got, tc.want) {
582 t.Errorf("%+v: got %+v, want %+v", tc.pval, got, tc.want)
583 }
584 })
585 }
586
587 t.Run("s1struct", func(t *testing.T) {
588 got, err := convertParamValue(&s1ParamValue, s1ParamType)
589 if err != nil {
590 t.Fatal(err)
591 }
592 if !testutil.Equal(got, s1ParamReturnValue) {
593 t.Errorf("got %+v, want %+v", got, s1ParamReturnValue)
594 }
595 })
596 }
597
598 func TestIntegration_ScalarParam(t *testing.T) {
599 roundToMicros := cmp.Transformer("RoundToMicros",
600 func(t time.Time) time.Time { return t.Round(time.Microsecond) })
601 c := getClient(t)
602 for _, tc := range scalarTests {
603 t.Run(tc.name, func(t *testing.T) {
604 gotData, gotParam, err := paramRoundTrip(c, tc.val)
605 if err != nil {
606 t.Errorf("input %#v errored: %v", tc.val, err)
607 }
608
609 if tc.wantNil {
610 if gotData != nil {
611 t.Errorf("data value %#v expected nil, got %#v", tc.val, gotData)
612 }
613 } else {
614 if !testutil.Equal(gotData, tc.wantStat, roundToMicros) {
615 t.Errorf("\ngot data value %#v (%T)\nwant %#v (%T)", gotData, gotData, tc.wantStat, tc.wantStat)
616 }
617 }
618
619 if !testutil.Equal(gotParam, tc.wantStat, roundToMicros) {
620 t.Errorf("\ngot param stat %#v (%T)\nwant %#v (%T)", gotParam, gotParam, tc.wantStat, tc.wantStat)
621 }
622 })
623 }
624 }
625
626 func TestIntegration_OtherParam(t *testing.T) {
627 c := getClient(t)
628 for _, tc := range []struct {
629 name string
630 val interface{}
631 wantData interface{}
632 wantParam interface{}
633 }{
634 {"nil", []int(nil), []Value(nil), []interface{}(nil)},
635 {"emptyIntSlice", []int{}, []Value(nil), []interface{}(nil)},
636 {
637 "intSlice",
638 []int{1, 2},
639 []Value{int64(1), int64(2)},
640 []interface{}{int64(1), int64(2)},
641 },
642 {
643 "intArray",
644 [3]int{1, 2, 3},
645 []Value{int64(1), int64(2), int64(3)},
646 []interface{}{int64(1), int64(2), int64(3)},
647 },
648 {
649 "emptyStruct",
650 S1{},
651 []Value{int64(0), nil, false},
652 map[string]interface{}{
653 "A": int64(0),
654 "B": nil,
655 "C": false,
656 },
657 },
658 {
659 "s1struct",
660 s1,
661 []Value{int64(1), []Value{"s"}, true},
662 s1ParamReturnValue,
663 },
664 {
665 "RangeTimestamp",
666 &RangeValue{
667 Start: time.Date(2016, 3, 22, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720)),
668 End: NullTimestamp{},
669 },
670 &RangeValue{
671 Start: time.Date(2016, 3, 22, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720)),
672 End: nil,
673 },
674 &RangeValue{
675 Start: time.Date(2016, 3, 22, 4, 22, 9, 5000, time.FixedZone("neg1-2", -3720)),
676 End: nil,
677 },
678 },
679 } {
680 t.Run(tc.name, func(t *testing.T) {
681 gotData, gotParam, err := paramRoundTrip(c, tc.val)
682 if err != nil {
683 t.Fatal(err)
684 }
685 if !testutil.Equal(gotData, tc.wantData) {
686 t.Errorf("%#v:\ngot %#v (%T)\nwant %#v (%T)",
687 tc.val, gotData, gotData, tc.wantData, tc.wantData)
688 }
689 if !testutil.Equal(gotParam, tc.wantParam) {
690 t.Errorf("%#v:\ngot %#v (%T)\nwant %#v (%T)",
691 tc.val, gotParam, gotParam, tc.wantParam, tc.wantParam)
692 }
693 })
694 }
695 }
696
697
698
699
700 func paramRoundTrip(c *Client, x interface{}) (data Value, param interface{}, err error) {
701 ctx := context.Background()
702 q := c.Query("select ?")
703 q.Parameters = []QueryParameter{{Value: x}}
704 job, err := q.Run(ctx)
705 if err != nil {
706 return nil, nil, err
707 }
708 it, err := job.Read(ctx)
709 if err != nil {
710 return nil, nil, err
711 }
712 var val []Value
713 err = it.Next(&val)
714 if err != nil {
715 return nil, nil, err
716 }
717 if len(val) != 1 {
718 return nil, nil, errors.New("wrong number of values")
719 }
720 conf, err := job.Config()
721 if err != nil {
722 return nil, nil, err
723 }
724 return val[0], conf.(*QueryConfig).Parameters[0].Value, nil
725 }
726
727 func TestQueryParameter_toBQ(t *testing.T) {
728 in := QueryParameter{Name: "name", Value: ""}
729 want := []string{"Value"}
730 q, err := in.toBQ()
731 if err != nil {
732 t.Fatalf("expected no error, got %v", err)
733 }
734
735 got := q.ParameterValue.ForceSendFields
736 if !cmp.Equal(want, got) {
737 t.Fatalf("want %v, got %v", want, got)
738 }
739 }
740
View as plain text