From ea3cde55ad3d8d843bce8d023747cf69552850b5 Mon Sep 17 00:00:00 2001 From: shollyman Date: Thu, 19 Nov 2020 14:18:03 -0800 Subject: [PATCH] feat(bigquery): add support for bignumeric (#2779) feat(bigquery): add support for bignumeric This PR adds basic support for the BIGNUMERIC type in BigQuery. This library has several cases where it tries to infer the appropriate BigQuery type from a native Go type. For big.Rat types, we continue the existing behavior of mapping them to NUMERIC, as the big.Rat doesn't provide a general way of indicating desired precision or scale to determine whether BIGNUMERIC is a more appropriate mapping. --- bigquery/integration_test.go | 28 ++++---- bigquery/params.go | 41 +++++++----- bigquery/schema.go | 32 +++++---- bigquery/schema_test.go | 6 +- bigquery/value.go | 34 +++++++++- bigquery/value_test.go | 124 +++++++++++++++++++++-------------- go.sum | 2 + 7 files changed, 172 insertions(+), 95 deletions(-) diff --git a/bigquery/integration_test.go b/bigquery/integration_test.go index 14573e2bb3a..60959d0a0ca 100644 --- a/bigquery/integration_test.go +++ b/bigquery/integration_test.go @@ -1434,6 +1434,7 @@ func TestIntegration_InsertAndReadNullable(t *testing.T) { ctm := civil.Time{Hour: 15, Minute: 4, Second: 5, Nanosecond: 6000} cdt := civil.DateTime{Date: testDate, Time: ctm} rat := big.NewRat(33, 100) + rat2 := big.NewRat(66, 100) geo := "POINT(-122.198939 47.669865)" // Nil fields in the struct. @@ -1455,20 +1456,21 @@ func TestIntegration_InsertAndReadNullable(t *testing.T) { // Populate the struct with values. testInsertAndReadNullable(t, testStructNullable{ - String: NullString{"x", true}, - Bytes: []byte{1, 2, 3}, - Integer: NullInt64{1, true}, - Float: NullFloat64{2.3, true}, - Boolean: NullBool{true, true}, - Timestamp: NullTimestamp{testTimestamp, true}, - Date: NullDate{testDate, true}, - Time: NullTime{ctm, true}, - DateTime: NullDateTime{cdt, true}, - Numeric: rat, - Geography: NullGeography{geo, true}, - Record: &subNullable{X: NullInt64{4, true}}, + String: NullString{"x", true}, + Bytes: []byte{1, 2, 3}, + Integer: NullInt64{1, true}, + Float: NullFloat64{2.3, true}, + Boolean: NullBool{true, true}, + Timestamp: NullTimestamp{testTimestamp, true}, + Date: NullDate{testDate, true}, + Time: NullTime{ctm, true}, + DateTime: NullDateTime{cdt, true}, + Numeric: rat, + BigNumeric: rat2, + Geography: NullGeography{geo, true}, + Record: &subNullable{X: NullInt64{4, true}}, }, - []Value{"x", []byte{1, 2, 3}, int64(1), 2.3, true, testTimestamp, testDate, ctm, cdt, rat, geo, []Value{int64(4)}}) + []Value{"x", []byte{1, 2, 3}, int64(1), 2.3, true, testTimestamp, testDate, ctm, cdt, rat, rat2, geo, []Value{int64(4)}}) } func testInsertAndReadNullable(t *testing.T, ts testStructNullable, wantRow []Value) { diff --git a/bigquery/params.go b/bigquery/params.go index 59570226189..68bb3fa01ac 100644 --- a/bigquery/params.go +++ b/bigquery/params.go @@ -65,16 +65,17 @@ func (e invalidFieldNameError) Error() string { var fieldCache = fields.NewCache(bqTagParser, nil, nil) var ( - int64ParamType = &bq.QueryParameterType{Type: "INT64"} - float64ParamType = &bq.QueryParameterType{Type: "FLOAT64"} - boolParamType = &bq.QueryParameterType{Type: "BOOL"} - stringParamType = &bq.QueryParameterType{Type: "STRING"} - bytesParamType = &bq.QueryParameterType{Type: "BYTES"} - dateParamType = &bq.QueryParameterType{Type: "DATE"} - timeParamType = &bq.QueryParameterType{Type: "TIME"} - dateTimeParamType = &bq.QueryParameterType{Type: "DATETIME"} - timestampParamType = &bq.QueryParameterType{Type: "TIMESTAMP"} - numericParamType = &bq.QueryParameterType{Type: "NUMERIC"} + int64ParamType = &bq.QueryParameterType{Type: "INT64"} + float64ParamType = &bq.QueryParameterType{Type: "FLOAT64"} + boolParamType = &bq.QueryParameterType{Type: "BOOL"} + stringParamType = &bq.QueryParameterType{Type: "STRING"} + bytesParamType = &bq.QueryParameterType{Type: "BYTES"} + dateParamType = &bq.QueryParameterType{Type: "DATE"} + timeParamType = &bq.QueryParameterType{Type: "TIME"} + dateTimeParamType = &bq.QueryParameterType{Type: "DATETIME"} + timestampParamType = &bq.QueryParameterType{Type: "TIMESTAMP"} + numericParamType = &bq.QueryParameterType{Type: "NUMERIC"} + bigNumericParamType = &bq.QueryParameterType{Type: "BIGNUMERIC"} ) var ( @@ -233,6 +234,9 @@ func paramValue(v reflect.Value) (bq.QueryParameterValue, error) { return res, nil case typeOfRat: + // big.Rat types don't communicate scale or precision, so we cannot + // disambiguate between NUMERIC and BIGNUMERIC. For now, we'll continue + // to honor previous behavior and send as Numeric type. res.Value = NumericString(v.Interface().(*big.Rat)) return res, nil } @@ -304,14 +308,15 @@ func bqToQueryParameter(q *bq.QueryParameter) (QueryParameter, error) { } var paramTypeToFieldType = map[string]FieldType{ - int64ParamType.Type: IntegerFieldType, - float64ParamType.Type: FloatFieldType, - boolParamType.Type: BooleanFieldType, - stringParamType.Type: StringFieldType, - bytesParamType.Type: BytesFieldType, - dateParamType.Type: DateFieldType, - timeParamType.Type: TimeFieldType, - numericParamType.Type: NumericFieldType, + int64ParamType.Type: IntegerFieldType, + float64ParamType.Type: FloatFieldType, + boolParamType.Type: BooleanFieldType, + stringParamType.Type: StringFieldType, + bytesParamType.Type: BytesFieldType, + dateParamType.Type: DateFieldType, + timeParamType.Type: TimeFieldType, + numericParamType.Type: NumericFieldType, + bigNumericParamType.Type: BigNumericFieldType, } // Convert a parameter value from the service to a Go value. This is similar to, but diff --git a/bigquery/schema.go b/bigquery/schema.go index a9a50ef0006..dcb5c6f9b77 100644 --- a/bigquery/schema.go +++ b/bigquery/schema.go @@ -182,23 +182,27 @@ const ( // GeographyFieldType is a string field type. Geography types represent a set of points // on the Earth's surface, represented in Well Known Text (WKT) format. GeographyFieldType FieldType = "GEOGRAPHY" + // BigNumericFieldType is a numeric field type that supports values of larger precision + // and scale than the NumericFieldType. + BigNumericFieldType FieldType = "BIGNUMERIC" ) var ( errEmptyJSONSchema = errors.New("bigquery: empty JSON schema") fieldTypes = map[FieldType]bool{ - StringFieldType: true, - BytesFieldType: true, - IntegerFieldType: true, - FloatFieldType: true, - BooleanFieldType: true, - TimestampFieldType: true, - RecordFieldType: true, - DateFieldType: true, - TimeFieldType: true, - DateTimeFieldType: true, - NumericFieldType: true, - GeographyFieldType: true, + StringFieldType: true, + BytesFieldType: true, + IntegerFieldType: true, + FloatFieldType: true, + BooleanFieldType: true, + TimestampFieldType: true, + RecordFieldType: true, + DateFieldType: true, + TimeFieldType: true, + DateTimeFieldType: true, + NumericFieldType: true, + GeographyFieldType: true, + BigNumericFieldType: true, } // The API will accept alias names for the types based on the Standard SQL type names. fieldAliases = map[FieldType]FieldType{ @@ -346,6 +350,10 @@ func inferFieldSchema(fieldName string, rt reflect.Type, nullable bool) (*FieldS case typeOfDateTime: return &FieldSchema{Required: true, Type: DateTimeFieldType}, nil case typeOfRat: + // We automatically infer big.Rat values as NUMERIC as we cannot + // determine precision/scale from the type. Users who want the + // larger precision of BIGNUMERIC need to manipulate the inferred + // schema. return &FieldSchema{Required: !nullable, Type: NumericFieldType}, nil } if ft := nullableFieldType(rt); ft != "" { diff --git a/bigquery/schema_test.go b/bigquery/schema_test.go index 5bd10849ac3..ae936980c78 100644 --- a/bigquery/schema_test.go +++ b/bigquery/schema_test.go @@ -1041,7 +1041,8 @@ func TestSchemaFromJSON(t *testing.T) { {"name":"flat_date","type":"DATE","mode":"NULLABLE","description":"Flat required DATE"}, {"name":"flat_time","type":"TIME","mode":"REQUIRED","description":"Flat nullable TIME"}, {"name":"flat_datetime","type":"DATETIME","mode":"NULLABLE","description":"Flat required DATETIME"}, - {"name":"flat_numeric","type":"NUMERIC","mode":"REQUIRED","description":"Flat nullable NUMERIC"}, + {"name":"flat_numeric","type":"NUMERIC","mode":"REQUIRED","description":"Flat required NUMERIC"}, + {"name":"flat_bignumeric","type":"BIGNUMERIC","mode":"NULLABLE","description":"Flat nullable BIGNUMERIC"}, {"name":"flat_geography","type":"GEOGRAPHY","mode":"REQUIRED","description":"Flat required GEOGRAPHY"}, {"name":"aliased_integer","type":"INT64","mode":"REQUIRED","description":"Aliased required integer"}, {"name":"aliased_boolean","type":"BOOL","mode":"NULLABLE","description":"Aliased nullable boolean"}, @@ -1058,7 +1059,8 @@ func TestSchemaFromJSON(t *testing.T) { fieldSchema("Flat required DATE", "flat_date", "DATE", false, false, nil), fieldSchema("Flat nullable TIME", "flat_time", "TIME", false, true, nil), fieldSchema("Flat required DATETIME", "flat_datetime", "DATETIME", false, false, nil), - fieldSchema("Flat nullable NUMERIC", "flat_numeric", "NUMERIC", false, true, nil), + fieldSchema("Flat required NUMERIC", "flat_numeric", "NUMERIC", false, true, nil), + fieldSchema("Flat nullable BIGNUMERIC", "flat_bignumeric", "BIGNUMERIC", false, false, nil), fieldSchema("Flat required GEOGRAPHY", "flat_geography", "GEOGRAPHY", false, true, nil), fieldSchema("Aliased required integer", "aliased_integer", "INTEGER", false, true, nil), fieldSchema("Aliased nullable boolean", "aliased_boolean", "BOOLEAN", false, false, nil), diff --git a/bigquery/value.go b/bigquery/value.go index 361341c2bb8..cae28be0464 100644 --- a/bigquery/value.go +++ b/bigquery/value.go @@ -407,6 +407,13 @@ func determineSetFunc(ftype reflect.Type, stype FieldType) setFunc { return setNull(v, x, func() interface{} { return x.(*big.Rat) }) } } + + case BigNumericFieldType: + if ftype == typeOfRat { + return func(v reflect.Value, x interface{}) error { + return setNull(v, x, func() interface{} { return x.(*big.Rat) }) + } + } } return nil } @@ -692,7 +699,7 @@ func structFieldToUploadValue(vfield reflect.Value, schemaField *FieldSchema) (i } func toUploadValue(val interface{}, fs *FieldSchema) interface{} { - if fs.Type == TimeFieldType || fs.Type == DateTimeFieldType || fs.Type == NumericFieldType { + if fs.Type == TimeFieldType || fs.Type == DateTimeFieldType || fs.Type == NumericFieldType || fs.Type == BigNumericFieldType { return toUploadValueReflect(reflect.ValueOf(val), fs) } return val @@ -721,6 +728,13 @@ func toUploadValueReflect(v reflect.Value, fs *FieldSchema) interface{} { return formatUploadValue(v, fs, func(v reflect.Value) string { return NumericString(v.Interface().(*big.Rat)) }) + case BigNumericFieldType: + if r, ok := v.Interface().(*big.Rat); ok && r == nil { + return nil + } + return formatUploadValue(v, fs, func(v reflect.Value) string { + return BigNumericString(v.Interface().(*big.Rat)) + }) default: if !fs.Repeated || v.Len() > 0 { return v.Interface() @@ -786,6 +800,12 @@ const ( // NumericScaleDigits is the maximum number of digits after the decimal point in a NUMERIC value. NumericScaleDigits = 9 + + // BigNumericPrecisionDigits is the maximum number of full digits in a BIGNUMERIC value. + BigNumericPrecisionDigits = 76 + + // BigNumericScaleDigits is the maximum number of full digits in a BIGNUMERIC value. + BigNumericScaleDigits = 38 ) // NumericString returns a string representing a *big.Rat in a format compatible @@ -795,6 +815,12 @@ func NumericString(r *big.Rat) string { return r.FloatString(NumericScaleDigits) } +// BigNumericString returns a string representing a *big.Rat in a format compatible with BigQuery +// SQL. It returns a floating point literal with 38 digits after the decimal point. +func BigNumericString(r *big.Rat) string { + return r.FloatString(BigNumericScaleDigits) +} + // convertRows converts a series of TableRows into a series of Value slices. // schema is used to interpret the data from rows; its length must match the // length of each row. @@ -913,6 +939,12 @@ func convertBasicType(val string, typ FieldType) (Value, error) { return nil, fmt.Errorf("bigquery: invalid NUMERIC value %q", val) } return Value(r), nil + case BigNumericFieldType: + r, ok := (&big.Rat{}).SetString(val) + if !ok { + return nil, fmt.Errorf("bigquery: invalid BIGNUMERIC value %q", val) + } + return Value(r), nil case GeographyFieldType: return val, nil default: diff --git a/bigquery/value_test.go b/bigquery/value_test.go index 1e5bb2d37fc..4d08336ed02 100644 --- a/bigquery/value_test.go +++ b/bigquery/value_test.go @@ -36,6 +36,7 @@ func TestConvertBasicValues(t *testing.T) { {Type: BooleanFieldType}, {Type: BytesFieldType}, {Type: NumericFieldType}, + {Type: BigNumericFieldType}, {Type: GeographyFieldType}, } row := &bq.TableRow{ @@ -46,6 +47,7 @@ func TestConvertBasicValues(t *testing.T) { {V: "true"}, {V: base64.StdEncoding.EncodeToString([]byte("foo"))}, {V: "123.123456789"}, + {V: "99999999999999999999999999999999999999.99999999999999999999999999999999999999"}, {V: testGeography}, }, } @@ -54,7 +56,9 @@ func TestConvertBasicValues(t *testing.T) { t.Fatalf("error converting: %v", err) } - want := []Value{"a", int64(1), 1.2, true, []byte("foo"), big.NewRat(123123456789, 1e9), testGeography} + bigRatVal := new(big.Rat) + bigRatVal.SetString("99999999999999999999999999999999999999.99999999999999999999999999999999999999") + want := []Value{"a", int64(1), 1.2, true, []byte("foo"), big.NewRat(123123456789, 1e9), bigRatVal, testGeography} if !testutil.Equal(got, want) { t.Errorf("converting basic values: got:\n%v\nwant:\n%v", got, want) } @@ -444,6 +448,7 @@ func TestValuesSaverConvertsToMap(t *testing.T) { {Name: "strField", Type: StringFieldType}, {Name: "dtField", Type: DateTimeFieldType}, {Name: "nField", Type: NumericFieldType}, + {Name: "bigNumField", Type: BigNumericFieldType}, {Name: "geoField", Type: GeographyFieldType}, }, InsertID: "iid", @@ -452,16 +457,18 @@ func TestValuesSaverConvertsToMap(t *testing.T) { Date: civil.Date{Year: 1, Month: 2, Day: 3}, Time: civil.Time{Hour: 4, Minute: 5, Second: 6, Nanosecond: 7000}}, big.NewRat(123456789000, 1e9), + big.NewRat(1, 3), testGeography, }, }, wantInsertID: "iid", wantRow: map[string]Value{ - "intField": 1, - "strField": "a", - "dtField": "0001-02-03 04:05:06.000007", - "nField": "123.456789000", - "geoField": testGeography, + "intField": 1, + "strField": "a", + "dtField": "0001-02-03 04:05:06.000007", + "nField": "123.456789000", + "bigNumField": "0.33333333333333333333333333333333333333", + "geoField": testGeography, }, }, { @@ -603,6 +610,8 @@ func TestStructSaver(t *testing.T) { {Name: "p", Type: IntegerFieldType, Required: false}, {Name: "n", Type: NumericFieldType, Required: false}, {Name: "nr", Type: NumericFieldType, Repeated: true}, + {Name: "bn", Type: BigNumericFieldType, Required: false}, + {Name: "bnr", Type: BigNumericFieldType, Repeated: true}, {Name: "g", Type: GeographyFieldType, Required: false}, {Name: "gr", Type: GeographyFieldType, Repeated: true}, } @@ -619,6 +628,8 @@ func TestStructSaver(t *testing.T) { P NullInt64 N *big.Rat NR []*big.Rat + BN *big.Rat + BNR []*big.Rat G NullGeography GR []string // Repeated Geography } @@ -654,6 +665,8 @@ func TestStructSaver(t *testing.T) { P: NullInt64{Valid: true, Int64: 17}, N: big.NewRat(123456, 1000), NR: []*big.Rat{big.NewRat(3, 1), big.NewRat(56789, 1e5)}, + BN: big.NewRat(1, 3), + BNR: []*big.Rat{big.NewRat(1, 3), big.NewRat(1, 2)}, G: NullGeography{Valid: true, GeographyVal: "POINT(-122.350220 47.649154)"}, GR: []string{"POINT(-122.350220 47.649154)", "POINT(-122.198939 47.669865)"}, } @@ -667,6 +680,8 @@ func TestStructSaver(t *testing.T) { "p": NullInt64{Valid: true, Int64: 17}, "n": "123.456000000", "nr": []string{"3.000000000", "0.567890000"}, + "bn": "0.33333333333333333333333333333333333333", + "bnr": []string{"0.33333333333333333333333333333333333333", "0.50000000000000000000000000000000000000"}, "g": NullGeography{Valid: true, GeographyVal: "POINT(-122.350220 47.649154)"}, "gr": []string{"POINT(-122.350220 47.649154)", "POINT(-122.198939 47.669865)"}, } @@ -729,20 +744,24 @@ func TestStructSaverErrors(t *testing.T) { } } -func TestNumericString(t *testing.T) { +func TestNumericStrings(t *testing.T) { for _, test := range []struct { - in *big.Rat - want string + description string + in *big.Rat + wantNumeric string + wantBigNumeric string }{ - {big.NewRat(2, 3), "0.666666667"}, // round to 9 places - {big.NewRat(1, 2), "0.500000000"}, - {big.NewRat(1, 2*1e8), "0.000000005"}, - {big.NewRat(5, 1e10), "0.000000001"}, // round up the 5 in the 10th decimal place - {big.NewRat(-5, 1e10), "-0.000000001"}, // round half away from zero + {"repeating with rounding", big.NewRat(2, 3), "0.666666667", "0.66666666666666666666666666666666666667"}, + {"all zero padding", big.NewRat(1, 2), "0.500000000", "0.50000000000000000000000000000000000000"}, + {"zero pad with digit", big.NewRat(1, 2*1e8), "0.000000005", "0.00000000500000000000000000000000000000"}, + {"smaller rounding case 1", big.NewRat(5, 1e10), "0.000000001", "0.00000000050000000000000000000000000000"}, + {"smaller rounding case 2", big.NewRat(-5, 1e10), "-0.000000001", "-0.00000000050000000000000000000000000000"}, } { - got := NumericString(test.in) - if got != test.want { - t.Errorf("%v: got %q, want %q", test.in, got, test.want) + if got := NumericString(test.in); got != test.wantNumeric { + t.Errorf("case %q, val %v as numeric: got %q, want %q", test.description, test.in, got, test.wantNumeric) + } + if got := BigNumericString(test.in); got != test.wantBigNumeric { + t.Errorf("case %q, val %v as bignumeric: got %q, want %q", test.description, test.in, got, test.wantBigNumeric) } } } @@ -887,6 +906,7 @@ var ( {Name: "T", Type: TimeFieldType}, {Name: "DT", Type: DateTimeFieldType}, {Name: "N", Type: NumericFieldType}, + {Name: "BN", Type: BigNumericFieldType}, {Name: "G", Type: GeographyFieldType}, {Name: "nested", Type: RecordFieldType, Schema: Schema{ {Name: "nestS", Type: StringFieldType}, @@ -895,16 +915,17 @@ var ( {Name: "t", Type: StringFieldType}, } - testTimestamp = time.Date(2016, 11, 5, 7, 50, 22, 8, time.UTC) - testDate = civil.Date{Year: 2016, Month: 11, Day: 5} - testTime = civil.Time{Hour: 7, Minute: 50, Second: 22, Nanosecond: 8} - testDateTime = civil.DateTime{Date: testDate, Time: testTime} - testNumeric = big.NewRat(123, 456) + testTimestamp = time.Date(2016, 11, 5, 7, 50, 22, 8, time.UTC) + testDate = civil.Date{Year: 2016, Month: 11, Day: 5} + testTime = civil.Time{Hour: 7, Minute: 50, Second: 22, Nanosecond: 8} + testDateTime = civil.DateTime{Date: testDate, Time: testTime} + testNumeric = big.NewRat(123, 456) + testBigNumeric = big.NewRat(456, 789) // testGeography is a WKT string representing a single point. testGeography = "POINT(-122.350220 47.649154)" testValues = []Value{"x", "y", []byte{1, 2, 3}, int64(7), int64(8), 3.14, true, - testTimestamp, testDate, testTime, testDateTime, testNumeric, testGeography, + testTimestamp, testDate, testTime, testDateTime, testNumeric, testBigNumeric, testGeography, []Value{"nested", int64(17)}, "z"} ) @@ -918,6 +939,7 @@ type testStruct1 struct { By []byte F float64 N *big.Rat + BN *big.Rat G string Nested nested Tagged string `bigquery:"t"` @@ -952,6 +974,7 @@ func TestStructLoader(t *testing.T) { S2: "y", By: []byte{1, 2, 3}, N: big.NewRat(123, 456), + BN: big.NewRat(456, 789), G: testGeography, Nested: nested{NestS: "nested", NestI: 17}, Tagged: "z", @@ -1043,18 +1066,19 @@ func TestStructLoaderRepeated(t *testing.T) { } type testStructNullable struct { - String NullString - Bytes []byte - Integer NullInt64 - Float NullFloat64 - Boolean NullBool - Timestamp NullTimestamp - Date NullDate - Time NullTime - DateTime NullDateTime - Numeric *big.Rat - Geography NullGeography - Record *subNullable + String NullString + Bytes []byte + Integer NullInt64 + Float NullFloat64 + Boolean NullBool + Timestamp NullTimestamp + Date NullDate + Time NullTime + DateTime NullDateTime + Numeric *big.Rat + BigNumeric *big.Rat + Geography NullGeography + Record *subNullable } type subNullable struct { @@ -1072,6 +1096,7 @@ var testStructNullableSchema = Schema{ {Name: "Time", Type: TimeFieldType, Required: false}, {Name: "DateTime", Type: DateTimeFieldType, Required: false}, {Name: "Numeric", Type: NumericFieldType, Required: false}, + {Name: "BigNumeric", Type: BigNumericFieldType, Required: false}, {Name: "Geography", Type: GeographyFieldType, Required: false}, {Name: "Record", Type: RecordFieldType, Required: false, Schema: Schema{ {Name: "X", Type: IntegerFieldType, Required: false}, @@ -1088,24 +1113,25 @@ func TestStructLoaderNullable(t *testing.T) { } nonnilVals := []Value{"x", []byte{1, 2, 3}, int64(1), 2.3, true, testTimestamp, testDate, testTime, - testDateTime, big.NewRat(1, 2), testGeography, []Value{int64(4)}} + testDateTime, big.NewRat(1, 2), big.NewRat(3, 4), testGeography, []Value{int64(4)}} // All ts fields are nil. Loading non-nil values will cause them all to // be allocated. mustLoad(t, &ts, testStructNullableSchema, nonnilVals) want = testStructNullable{ - String: NullString{StringVal: "x", Valid: true}, - Bytes: []byte{1, 2, 3}, - Integer: NullInt64{Int64: 1, Valid: true}, - Float: NullFloat64{Float64: 2.3, Valid: true}, - Boolean: NullBool{Bool: true, Valid: true}, - Timestamp: NullTimestamp{Timestamp: testTimestamp, Valid: true}, - Date: NullDate{Date: testDate, Valid: true}, - Time: NullTime{Time: testTime, Valid: true}, - DateTime: NullDateTime{DateTime: testDateTime, Valid: true}, - Numeric: big.NewRat(1, 2), - Geography: NullGeography{GeographyVal: testGeography, Valid: true}, - Record: &subNullable{X: NullInt64{Int64: 4, Valid: true}}, + String: NullString{StringVal: "x", Valid: true}, + Bytes: []byte{1, 2, 3}, + Integer: NullInt64{Int64: 1, Valid: true}, + Float: NullFloat64{Float64: 2.3, Valid: true}, + Boolean: NullBool{Bool: true, Valid: true}, + Timestamp: NullTimestamp{Timestamp: testTimestamp, Valid: true}, + Date: NullDate{Date: testDate, Valid: true}, + Time: NullTime{Time: testTime, Valid: true}, + DateTime: NullDateTime{DateTime: testDateTime, Valid: true}, + Numeric: big.NewRat(1, 2), + BigNumeric: big.NewRat(3, 4), + Geography: NullGeography{GeographyVal: testGeography, Valid: true}, + Record: &subNullable{X: NullInt64{Int64: 4, Valid: true}}, } if diff := testutil.Diff(ts, want); diff != "" { t.Error(diff) @@ -1114,7 +1140,7 @@ func TestStructLoaderNullable(t *testing.T) { // Struct pointers are reused, byte slices are not. want = ts want.Bytes = []byte{17} - vals2 := []Value{nil, []byte{17}, nil, nil, nil, nil, nil, nil, nil, nil, nil, []Value{int64(7)}} + vals2 := []Value{nil, []byte{17}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []Value{int64(7)}} mustLoad(t, &ts, testStructNullableSchema, vals2) if ts.Record != want.Record { t.Error("record pointers not identical") diff --git a/go.sum b/go.sum index ea567ca6a70..b75391c1aae 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0 h1:a/O/bK/vWrYGOTFtH8di4rBxMZnmkjy+Y5LxpDwo+dA= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -348,6 +349,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuA golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=