Skip to content

Commit 8c1fb7d

Browse files
authored
feat(bigquery): switch all timestamp representations to int64 usec (#9368)
With the recent resolutions to the discovery representation, we now correctly propagate the options for controlling timestamp representation. This PR forces all usages to lossless representations.
1 parent 261c8d9 commit 8c1fb7d

File tree

6 files changed

+16
-50
lines changed

6 files changed

+16
-50
lines changed

bigquery/iterator.go

+2
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ func fetchTableResultPage(ctx context.Context, src *rowSource, schema Schema, st
280280
}()
281281
}
282282
call := src.t.c.bqs.Tabledata.List(src.t.ProjectID, src.t.DatasetID, src.t.TableID)
283+
call = call.FormatOptionsUseInt64Timestamp(true)
283284
setClientHeader(call.Header())
284285
if pageToken != "" {
285286
call.PageToken(pageToken)
@@ -317,6 +318,7 @@ func fetchJobResultPage(ctx context.Context, src *rowSource, schema Schema, star
317318
// reduce data transfered by leveraging api projections
318319
projectedFields := []googleapi.Field{"rows", "pageToken", "totalRows"}
319320
call := src.j.c.bqs.Jobs.GetQueryResults(src.j.projectID, src.j.jobID).Location(src.j.location).Context(ctx)
321+
call = call.FormatOptionsUseInt64Timestamp(true)
320322
if schema == nil {
321323
// only project schema if we weren't supplied one.
322324
projectedFields = append(projectedFields, "schema")

bigquery/job.go

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ func (j *Job) read(ctx context.Context, waitForQuery func(context.Context, strin
353353
func (j *Job) waitForQuery(ctx context.Context, projectID string) (Schema, uint64, error) {
354354
// Use GetQueryResults only to wait for completion, not to read results.
355355
call := j.c.bqs.Jobs.GetQueryResults(projectID, j.jobID).Location(j.location).Context(ctx).MaxResults(0)
356+
call = call.FormatOptionsUseInt64Timestamp(true)
356357
setClientHeader(call.Header())
357358
backoff := gax.Backoff{
358359
Initial: 1 * time.Second,

bigquery/query.go

+3
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ func (q *Query) probeFastPath() (*bq.QueryRequest, error) {
470470
MaximumBytesBilled: q.QueryConfig.MaxBytesBilled,
471471
RequestId: uid.NewSpace("request", nil).New(),
472472
Labels: q.Labels,
473+
FormatOptions: &bq.DataFormatOptions{
474+
UseInt64Timestamp: true,
475+
},
473476
}
474477
if q.QueryConfig.DisableQueryCache {
475478
qRequest.UseQueryCache = &pfalse

bigquery/query_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ func TestProbeFastPath(t *testing.T) {
439439
wantReq: &bq.QueryRequest{
440440
Query: "foo",
441441
UseLegacySql: &pfalse,
442+
FormatOptions: &bq.DataFormatOptions{
443+
UseInt64Timestamp: true,
444+
},
442445
},
443446
},
444447
{
@@ -473,6 +476,9 @@ func TestProbeFastPath(t *testing.T) {
473476
},
474477
},
475478
UseQueryCache: &pfalse,
479+
FormatOptions: &bq.DataFormatOptions{
480+
UseInt64Timestamp: true,
481+
},
476482
},
477483
},
478484
{

bigquery/value.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"encoding/base64"
1919
"errors"
2020
"fmt"
21-
"math"
2221
"math/big"
2322
"reflect"
2423
"strconv"
@@ -955,15 +954,11 @@ func convertBasicType(val string, typ FieldType) (Value, error) {
955954
case BooleanFieldType:
956955
return strconv.ParseBool(val)
957956
case TimestampFieldType:
958-
f, err := strconv.ParseFloat(val, 64)
957+
i, err := strconv.ParseInt(val, 10, 64)
959958
if err != nil {
960959
return nil, err
961960
}
962-
secs := math.Trunc(f)
963-
// Timestamps in BigQuery have microsecond precision, so we must
964-
// return a round number of microseconds.
965-
micros := math.Trunc((f-secs)*1e6 + 0.5)
966-
return Value(time.Unix(int64(secs), int64(micros)*1000).UTC()), nil
961+
return time.UnixMicro(i), nil
967962
case DateFieldType:
968963
return civil.ParseDate(val)
969964
case TimeFieldType:

bigquery/value_test.go

+2-43
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func TestConvertTime(t *testing.T) {
7878
ts := testTimestamp.Round(time.Millisecond)
7979
row := &bq.TableRow{
8080
F: []*bq.TableCell{
81-
{V: fmt.Sprintf("%.10f", float64(ts.UnixNano())/1e9)},
81+
{V: fmt.Sprint(ts.UnixMicro())},
8282
{V: testDate.String()},
8383
{V: testTime.String()},
8484
{V: testDateTime.String()},
@@ -95,15 +95,12 @@ func TestConvertTime(t *testing.T) {
9595
t.Errorf("#%d: got:\n%v\nwant:\n%v", i, g, w)
9696
}
9797
}
98-
if got[0].(time.Time).Location() != time.UTC {
99-
t.Errorf("expected time zone UTC: got:\n%v", got)
100-
}
10198
}
10299

103100
func TestConvertSmallTimes(t *testing.T) {
104101
for _, year := range []int{1600, 1066, 1} {
105102
want := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
106-
s := fmt.Sprintf("%.10f", float64(want.Unix()))
103+
s := fmt.Sprint(time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC).UnixMicro())
107104
got, err := convertBasicType(s, TimestampFieldType)
108105
if err != nil {
109106
t.Fatal(err)
@@ -114,44 +111,6 @@ func TestConvertSmallTimes(t *testing.T) {
114111
}
115112
}
116113

117-
func TestConvertTimePrecision(t *testing.T) {
118-
tcs := []struct {
119-
// Internally, BigQuery stores timestamps as microsecond-precision
120-
// floats.
121-
bq float64
122-
want time.Time
123-
}{
124-
{
125-
bq: 1555593697.154358,
126-
want: time.Unix(1555593697, 154358*1000),
127-
},
128-
{
129-
bq: 1555593697.154359,
130-
want: time.Unix(1555593697, 154359*1000),
131-
},
132-
{
133-
bq: 1555593697.154360,
134-
want: time.Unix(1555593697, 154360*1000),
135-
},
136-
}
137-
for _, tc := range tcs {
138-
bqS := fmt.Sprintf("%.6f", tc.bq)
139-
t.Run(bqS, func(t *testing.T) {
140-
got, err := convertBasicType(bqS, TimestampFieldType)
141-
if err != nil {
142-
t.Fatalf("convertBasicType failed: %v", err)
143-
}
144-
gotT, ok := got.(time.Time)
145-
if !ok {
146-
t.Fatalf("got a %T from convertBasicType, want a time.Time; got = %v", got, got)
147-
}
148-
if !gotT.Equal(tc.want) {
149-
t.Errorf("got %v from convertBasicType, want %v", gotT, tc.want)
150-
}
151-
})
152-
}
153-
}
154-
155114
func TestConvertNullValues(t *testing.T) {
156115
schema := Schema{{Type: StringFieldType}}
157116
row := &bq.TableRow{

0 commit comments

Comments
 (0)