...

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

Documentation: cloud.google.com/go/bigquery

     1  // Copyright 2015 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  	"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  	// Ensure that the times are returned in UTC timezone.
   101  	// https://github.com/googleapis/google-cloud-go/issues/9407
   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{}{ // repeated records.
   219  				map[string]interface{}{ // first record.
   220  					"v": map[string]interface{}{ // pointless single-key-map wrapper.
   221  						"f": []interface{}{ // list of record fields.
   222  							map[string]interface{}{ // only record (repeated ints)
   223  								"v": []interface{}{ // pointless wrapper.
   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{}{ // second record.
   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{ // the row is a list of length 1, containing an entry for the repeated record.
   266  		[]Value{ // the repeated record is a list of length 2, containing an entry for each repetition.
   267  			[]Value{ // the record is a list of length 1, containing an entry for the repeated integer field.
   268  				[]Value{int64(1), int64(2), int64(3)}, // the repeated integer field is a list of length 3.
   269  			},
   270  			[]Value{ // second record
   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{}{ // repeated records.
   302  				map[string]interface{}{ // first record.
   303  					"v": map[string]interface{}{ // pointless single-key-map wrapper.
   304  						"f": []interface{}{ // list of record fields.
   305  							map[string]interface{}{ // first record field (name)
   306  								"v": "first repeated record",
   307  							},
   308  							map[string]interface{}{ // second record field (nested record).
   309  								"v": map[string]interface{}{ // pointless single-key-map wrapper.
   310  									"f": []interface{}{ // nested record fields
   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{}{ // second record.
   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  	// TODO: test with flattenresults.
   353  	want := []Value{ // the row is a list of length 1, containing an entry for the repeated record.
   354  		[]Value{ // the repeated record is a list of length 2, containing an entry for each repetition.
   355  			[]Value{ // record contains a string followed by a nested record.
   356  				"first repeated record",
   357  				[]Value{
   358  					int64(1),
   359  					"two",
   360  				},
   361  			},
   362  			[]Value{ // second record.
   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  	// mismatched lengths
   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}}, // not a string
   387  		{[]interface{}{v3}, // not a string, repeated
   388  			FieldSchema{Type: IntegerFieldType, Repeated: true}},
   389  		{map[string]interface{}{"f": []interface{}{v3}}, // not a string, nested
   390  			FieldSchema{Type: RecordFieldType, Schema: Schema{{Type: IntegerFieldType}}}},
   391  		{map[string]interface{}{"f": []interface{}{v3}}, // wrong length, nested
   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  	// bad field type
   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  		{ // repeated nested field
   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{ // a row is a []Value
   486  					[]Value{ // repeated field's value is a []Value
   487  						[]Value{1, 2}, // first record of the repeated field
   488  						[]Value{3, 4}, // second record
   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  		{ // zero-length repeated nested field
   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{ // a row is a []Value
   516  					[]Value{}, // repeated field's value is a []Value, and non-nil
   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  		{ // mismatched length
   547  			[]Value{1},
   548  			Schema{},
   549  		},
   550  		{ // nested record not a slice
   551  			[]Value{1},
   552  			Schema{{Type: RecordFieldType}},
   553  		},
   554  		{ // nested record mismatched length
   555  			[]Value{[]Value{1}},
   556  			Schema{{Type: RecordFieldType}},
   557  		},
   558  		{ // nested repeated record not a slice
   559  			[]Value{[]Value{1}},
   560  			Schema{{Type: RecordFieldType, Repeated: true}},
   561  		},
   562  		{ // nested repeated record mismatched length
   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 // Repeated Geography
   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  	// Missing and extra fields ignored.
   669  	type T2 struct {
   670  		S string
   671  		// missing R, Nested, RNested
   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},           // not a struct
   709  		{&badField{}, nil}, // bad field name
   710  		{&badR{}, Schema{{Name: "r", Repeated: true}}},        // repeated field has bad type
   711  		{&badR{}, Schema{{Name: "r", Type: RecordFieldType}}}, // nested field has bad type
   712  		{&badRN{[]int{0}}, // nested repeated field has bad type
   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  	// Load truncates, not appends.
   809  	// https://github.com/googleapis/google-cloud-go/issues/437
   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  	// For testing StructLoader
   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  	// testGeography is a WKT string representing a single point.
   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  	// Note: the schema field named "s" gets matched to the exported struct
   944  	// field "S", not the unexported "s".
   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  	// Test pointers to nested structs.
   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  	// Existing values should be reused.
   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 // to test truncation
   988  	LongNums  [5]int // to test padding with zeroes
   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}, // extra values discarded
  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},    // truncated to zero and appended to
  1025  		LongNums: [...]int{-1, -2, -3, -4, -5}, // unset elements are zeroed
  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  	// Short slice case.
  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  	// All ts fields are nil. Loading non-nil values will cause them all to
  1097  	// be allocated.
  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  	// Struct pointers are reused, byte slices are not.
  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  	// It's OK if the struct has fields that the schema does not, and vice versa.
  1155  	type S1 struct {
  1156  		I int
  1157  		X [][]int // not in the schema; does not even correspond to a valid BigQuery type
  1158  		// many schema fields missing
  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  	// It's even valid to have no overlapping fields at all.
  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 } // wrong type for FLOAT column
  1190  	check(&bad1{})
  1191  
  1192  	type bad2 struct{ I uint } // unsupported integer type
  1193  	check(&bad2{})
  1194  
  1195  	type bad3 struct {
  1196  		I int `bigquery:"@"`
  1197  	} // bad field name
  1198  	check(&bad3{})
  1199  
  1200  	type bad4 struct{ Nested int } // non-struct for nested field
  1201  	check(&bad4{})
  1202  
  1203  	type bad5 struct{ Nested struct{ NestS int } } // bad nested struct
  1204  	check(&bad5{})
  1205  
  1206  	bad6 := &struct{ Nums int }{} // non-slice for repeated field
  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  	// sl.set's error is sticky, even with good input.
  1214  	err2 := sl.set(&repStruct{}, repSchema)
  1215  	if err2 != err {
  1216  		t.Errorf("%v != %v, expected equal", err2, err)
  1217  	}
  1218  	// sl.Load is similarly sticky
  1219  	err2 = sl.Load(nil, nil)
  1220  	if err2 != err {
  1221  		t.Errorf("%v != %v, expected equal", err2, err)
  1222  	}
  1223  
  1224  	// Null values.
  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  	// Using more than one struct type with the same structLoader.
  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