...

Source file src/cloud.google.com/go/bigquery/params_test.go

Documentation: cloud.google.com/go/bigquery

     1  // Copyright 2016 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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{}            // input value sent as query param
    36  	wantNil  bool                   // whether the value returned in a query field should be nil.
    37  	wantVal  string                 // the string form of the scalar value in QueryParameterValue.
    38  	wantType *bq.QueryParameterType // paramType's desired output
    39  	wantStat interface{}            // val when roundtripped and represented as part of job statistics.
    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")}, // base64 encoding of "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  	// paramValue lets a few invalid types through, but paramType catches them.
   421  	// Since we never call one without the other that's fine.
   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  	// Recursive structs
   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  	// Scalars.
   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  	// Arrays.
   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  	// Structs.
   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  			// first, check the returned query value
   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  			// then, check the stat value
   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  // paramRoundTrip passes x as a query parameter to BigQuery. It returns
   698  // the resulting data value from running the query and the parameter value from
   699  // the returned job configuration.
   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