| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package bigquery |
| |
| import ( |
| "errors" |
| "reflect" |
| "testing" |
| |
| "golang.org/x/net/context" |
| bq "google.golang.org/api/bigquery/v2" |
| "google.golang.org/api/iterator" |
| ) |
| |
| type readTabledataArgs struct { |
| conf *readTableConf |
| tok string |
| } |
| |
| // readServiceStub services read requests by returning data from an in-memory list of values. |
| type readServiceStub struct { |
| // values and pageTokens are used as sources of data to return in response to calls to readTabledata or readQuery. |
| values [][][]Value // contains pages / rows / columns. |
| pageTokens map[string]string // maps incoming page token to returned page token. |
| |
| // arguments are recorded for later inspection. |
| readTabledataCalls []readTabledataArgs |
| |
| service |
| } |
| |
| func (s *readServiceStub) readValues(tok string) *readDataResult { |
| result := &readDataResult{ |
| pageToken: s.pageTokens[tok], |
| rows: s.values[0], |
| } |
| s.values = s.values[1:] |
| |
| return result |
| } |
| |
| func (s *readServiceStub) waitForQuery(ctx context.Context, projectID, jobID string) (Schema, error) { |
| return nil, nil |
| } |
| |
| func (s *readServiceStub) readTabledata(ctx context.Context, conf *readTableConf, token string) (*readDataResult, error) { |
| s.readTabledataCalls = append(s.readTabledataCalls, readTabledataArgs{conf, token}) |
| return s.readValues(token), nil |
| } |
| |
| func TestRead(t *testing.T) { |
| // The data for the service stub to return is populated for each test case in the testCases for loop. |
| ctx := context.Background() |
| service := &readServiceStub{} |
| c := &Client{ |
| projectID: "project-id", |
| service: service, |
| } |
| |
| queryJob := &Job{ |
| projectID: "project-id", |
| jobID: "job-id", |
| c: c, |
| isQuery: true, |
| destinationTable: &bq.TableReference{ |
| ProjectId: "project-id", |
| DatasetId: "dataset-id", |
| TableId: "table-id", |
| }, |
| } |
| |
| for _, readFunc := range []func() *RowIterator{ |
| func() *RowIterator { |
| return c.Dataset("dataset-id").Table("table-id").Read(ctx) |
| }, |
| func() *RowIterator { |
| it, err := queryJob.Read(ctx) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return it |
| }, |
| } { |
| testCases := []struct { |
| data [][][]Value |
| pageTokens map[string]string |
| want [][]Value |
| }{ |
| { |
| data: [][][]Value{{{1, 2}, {11, 12}}, {{30, 40}, {31, 41}}}, |
| pageTokens: map[string]string{"": "a", "a": ""}, |
| want: [][]Value{{1, 2}, {11, 12}, {30, 40}, {31, 41}}, |
| }, |
| { |
| data: [][][]Value{{{1, 2}, {11, 12}}, {{30, 40}, {31, 41}}}, |
| pageTokens: map[string]string{"": ""}, // no more pages after first one. |
| want: [][]Value{{1, 2}, {11, 12}}, |
| }, |
| } |
| for _, tc := range testCases { |
| service.values = tc.data |
| service.pageTokens = tc.pageTokens |
| if got, ok := collectValues(t, readFunc()); ok { |
| if !reflect.DeepEqual(got, tc.want) { |
| t.Errorf("reading: got:\n%v\nwant:\n%v", got, tc.want) |
| } |
| } |
| } |
| } |
| } |
| |
| func collectValues(t *testing.T, it *RowIterator) ([][]Value, bool) { |
| var got [][]Value |
| for { |
| var vals []Value |
| err := it.Next(&vals) |
| if err == iterator.Done { |
| break |
| } |
| if err != nil { |
| t.Errorf("err calling Next: %v", err) |
| return nil, false |
| } |
| got = append(got, vals) |
| } |
| return got, true |
| } |
| |
| func TestNoMoreValues(t *testing.T) { |
| c := &Client{ |
| projectID: "project-id", |
| service: &readServiceStub{ |
| values: [][][]Value{{{1, 2}, {11, 12}}}, |
| }, |
| } |
| it := c.Dataset("dataset-id").Table("table-id").Read(context.Background()) |
| var vals []Value |
| // We expect to retrieve two values and then fail on the next attempt. |
| if err := it.Next(&vals); err != nil { |
| t.Fatalf("Next: got: %v: want: nil", err) |
| } |
| if err := it.Next(&vals); err != nil { |
| t.Fatalf("Next: got: %v: want: nil", err) |
| } |
| if err := it.Next(&vals); err != iterator.Done { |
| t.Fatalf("Next: got: %v: want: iterator.Done", err) |
| } |
| } |
| |
| type errorReadService struct { |
| service |
| } |
| |
| var errBang = errors.New("bang!") |
| |
| func (s *errorReadService) readTabledata(ctx context.Context, conf *readTableConf, token string) (*readDataResult, error) { |
| return nil, errBang |
| } |
| |
| func TestReadError(t *testing.T) { |
| // test that service read errors are propagated back to the caller. |
| c := &Client{ |
| projectID: "project-id", |
| service: &errorReadService{}, |
| } |
| it := c.Dataset("dataset-id").Table("table-id").Read(context.Background()) |
| var vals []Value |
| if err := it.Next(&vals); err != errBang { |
| t.Fatalf("Get: got: %v: want: %v", err, errBang) |
| } |
| } |
| |
| func TestReadTabledataOptions(t *testing.T) { |
| // test that read options are propagated. |
| s := &readServiceStub{ |
| values: [][][]Value{{{1, 2}}}, |
| } |
| c := &Client{ |
| projectID: "project-id", |
| service: s, |
| } |
| it := c.Dataset("dataset-id").Table("table-id").Read(context.Background()) |
| it.PageInfo().MaxSize = 5 |
| var vals []Value |
| if err := it.Next(&vals); err != nil { |
| t.Fatal(err) |
| } |
| want := []readTabledataArgs{{ |
| conf: &readTableConf{ |
| projectID: "project-id", |
| datasetID: "dataset-id", |
| tableID: "table-id", |
| paging: pagingConf{ |
| recordsPerRequest: 5, |
| setRecordsPerRequest: true, |
| }, |
| }, |
| tok: "", |
| }} |
| |
| if !reflect.DeepEqual(s.readTabledataCalls, want) { |
| t.Errorf("reading: got:\n%v\nwant:\n%v", s.readTabledataCalls, want) |
| } |
| } |
| |
| func TestReadQueryOptions(t *testing.T) { |
| // test that read options are propagated. |
| s := &readServiceStub{ |
| values: [][][]Value{{{1, 2}}}, |
| } |
| queryJob := &Job{ |
| projectID: "project-id", |
| jobID: "job-id", |
| c: &Client{service: s}, |
| isQuery: true, |
| destinationTable: &bq.TableReference{ |
| ProjectId: "project-id", |
| DatasetId: "dataset-id", |
| TableId: "table-id", |
| }, |
| } |
| it, err := queryJob.Read(context.Background()) |
| if err != nil { |
| t.Fatalf("err calling Read: %v", err) |
| } |
| it.PageInfo().MaxSize = 5 |
| var vals []Value |
| if err := it.Next(&vals); err != nil { |
| t.Fatalf("Next: got: %v: want: nil", err) |
| } |
| |
| want := []readTabledataArgs{{ |
| conf: &readTableConf{ |
| projectID: "project-id", |
| datasetID: "dataset-id", |
| tableID: "table-id", |
| paging: pagingConf{ |
| recordsPerRequest: 5, |
| setRecordsPerRequest: true, |
| }, |
| }, |
| tok: "", |
| }} |
| |
| if !reflect.DeepEqual(s.readTabledataCalls, want) { |
| t.Errorf("reading: got:\n%v\nwant:\n%v", s.readTabledataCalls, want) |
| } |
| } |