// Copyright 2015 Google LLC // // 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 ( "context" "errors" "fmt" "testing" "cloud.google.com/go/internal/testutil" "google.golang.org/api/iterator" ) type fetchResponse struct { result *fetchPageResult // The result to return. err error // The error to return. } // pageFetcherStub services fetch requests by returning data from an in-memory list of values. type pageFetcherStub struct { fetchResponses map[string]fetchResponse err error } func (pf *pageFetcherStub) fetchPage(ctx context.Context, _ *Table, _ Schema, _ uint64, _ int64, pageToken string) (*fetchPageResult, error) { call, ok := pf.fetchResponses[pageToken] if !ok { pf.err = fmt.Errorf("Unexpected page token: %q", pageToken) } return call.result, call.err } func TestIterator(t *testing.T) { var ( iiSchema = Schema{ {Type: IntegerFieldType}, {Type: IntegerFieldType}, } siSchema = Schema{ {Type: StringFieldType}, {Type: IntegerFieldType}, } ) fetchFailure := errors.New("fetch failure") testCases := []struct { desc string pageToken string fetchResponses map[string]fetchResponse want [][]Value wantErr error wantSchema Schema wantTotalRows uint64 }{ { desc: "Iteration over single empty page", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{}, schema: Schema{}, }, }, }, want: [][]Value{}, wantSchema: Schema{}, }, { desc: "Iteration over single page", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{{1, 2}, {11, 12}}, schema: iiSchema, totalRows: 4, }, }, }, want: [][]Value{{1, 2}, {11, 12}}, wantSchema: iiSchema, wantTotalRows: 4, }, { desc: "Iteration over single page with different schema", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{{"1", 2}, {"11", 12}}, schema: siSchema, }, }, }, want: [][]Value{{"1", 2}, {"11", 12}}, wantSchema: siSchema, }, { desc: "Iteration over two pages", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "a", rows: [][]Value{{1, 2}, {11, 12}}, schema: iiSchema, totalRows: 4, }, }, "a": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{{101, 102}, {111, 112}}, schema: iiSchema, totalRows: 4, }, }, }, want: [][]Value{{1, 2}, {11, 12}, {101, 102}, {111, 112}}, wantSchema: iiSchema, wantTotalRows: 4, }, { desc: "Server response includes empty page", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "a", rows: [][]Value{{1, 2}, {11, 12}}, schema: iiSchema, }, }, "a": { result: &fetchPageResult{ pageToken: "b", rows: [][]Value{}, schema: iiSchema, }, }, "b": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{{101, 102}, {111, 112}}, schema: iiSchema, }, }, }, want: [][]Value{{1, 2}, {11, 12}, {101, 102}, {111, 112}}, wantSchema: iiSchema, }, { desc: "Fetch error", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "a", rows: [][]Value{{1, 2}, {11, 12}}, schema: iiSchema, }, }, "a": { // We returns some data from this fetch, but also an error. // So the end result should include only data from the previous fetch. err: fetchFailure, result: &fetchPageResult{ pageToken: "b", rows: [][]Value{{101, 102}, {111, 112}}, schema: iiSchema, }, }, }, want: [][]Value{{1, 2}, {11, 12}}, wantErr: fetchFailure, wantSchema: iiSchema, }, { desc: "Skip over an entire page", pageToken: "a", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "a", rows: [][]Value{{1, 2}, {11, 12}}, schema: iiSchema, }, }, "a": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{{101, 102}, {111, 112}}, schema: iiSchema, }, }, }, want: [][]Value{{101, 102}, {111, 112}}, wantSchema: iiSchema, }, { desc: "Skip beyond all data", pageToken: "b", fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "a", rows: [][]Value{{1, 2}, {11, 12}}, schema: iiSchema, }, }, "a": { result: &fetchPageResult{ pageToken: "b", rows: [][]Value{{101, 102}, {111, 112}}, schema: iiSchema, }, }, "b": { result: &fetchPageResult{}, }, }, // In this test case, Next will return false on its first call, // so we won't even attempt to call Get. want: [][]Value{}, wantSchema: Schema{}, }, } for _, tc := range testCases { pf := &pageFetcherStub{ fetchResponses: tc.fetchResponses, } it := newRowIterator(context.Background(), nil, pf.fetchPage) it.PageInfo().Token = tc.pageToken values, schema, totalRows, err := consumeRowIterator(it) if err != tc.wantErr { t.Fatalf("%s: got %v, want %v", tc.desc, err, tc.wantErr) } if (len(values) != 0 || len(tc.want) != 0) && !testutil.Equal(values, tc.want) { t.Errorf("%s: values:\ngot: %v\nwant:%v", tc.desc, values, tc.want) } if (len(schema) != 0 || len(tc.wantSchema) != 0) && !testutil.Equal(schema, tc.wantSchema) { t.Errorf("%s: iterator.Schema:\ngot: %v\nwant: %v", tc.desc, schema, tc.wantSchema) } if totalRows != tc.wantTotalRows { t.Errorf("%s: totalRows: got %d, want %d", tc.desc, totalRows, tc.wantTotalRows) } } } // consumeRowIterator reads the schema and all values from a RowIterator and returns them. func consumeRowIterator(it *RowIterator) ([][]Value, Schema, uint64, error) { var ( got [][]Value schema Schema totalRows uint64 ) for { var vls []Value err := it.Next(&vls) if err == iterator.Done { return got, schema, totalRows, nil } if err != nil { return got, schema, totalRows, err } got = append(got, vls) schema = it.Schema totalRows = it.TotalRows } } func TestNextDuringErrorState(t *testing.T) { pf := &pageFetcherStub{ fetchResponses: map[string]fetchResponse{ "": {err: errors.New("bang")}, }, } it := newRowIterator(context.Background(), nil, pf.fetchPage) var vals []Value if err := it.Next(&vals); err == nil { t.Errorf("Expected error after calling Next") } if err := it.Next(&vals); err == nil { t.Errorf("Expected error calling Next again when iterator has a non-nil error.") } } func TestNextAfterFinished(t *testing.T) { testCases := []struct { fetchResponses map[string]fetchResponse want [][]Value }{ { fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{{1, 2}, {11, 12}}, }, }, }, want: [][]Value{{1, 2}, {11, 12}}, }, { fetchResponses: map[string]fetchResponse{ "": { result: &fetchPageResult{ pageToken: "", rows: [][]Value{}, }, }, }, want: [][]Value{}, }, } for _, tc := range testCases { pf := &pageFetcherStub{ fetchResponses: tc.fetchResponses, } it := newRowIterator(context.Background(), nil, pf.fetchPage) values, _, _, err := consumeRowIterator(it) if err != nil { t.Fatal(err) } if (len(values) != 0 || len(tc.want) != 0) && !testutil.Equal(values, tc.want) { t.Errorf("values: got:\n%v\nwant:\n%v", values, tc.want) } // Try calling Get again. var vals []Value if err := it.Next(&vals); err != iterator.Done { t.Errorf("Expected Done calling Next when there are no more values") } } } func TestIteratorNextTypes(t *testing.T) { it := newRowIterator(context.Background(), nil, nil) for _, v := range []interface{}{3, "s", []int{}, &[]int{}, map[string]Value{}, &map[string]interface{}{}, struct{}{}, } { if err := it.Next(v); err == nil { t.Errorf("%v: want error, got nil", v) } } }