Skip to content

Commit

Permalink
feat: expose the query source of a rowiterator via SourceJob() (#4748)
Browse files Browse the repository at this point in the history
RowIterator can currently come from calling Read() on a Table,
Read() on a Job, and Read() directly from a Query.  The third invocation
is used for fast path queries and doesn't return a query identifier
suitable for gathering statistics.

This PR exposes a SourceJob() method on RowIterator to address this
issue.  Users still get the benefits of optimized query execution, but
can get the reference to a Job for looking up additional statistics
should they need it.

Fixes: #4745
  • Loading branch information
shollyman committed Sep 15, 2021
1 parent 4d9c046 commit 868d07d
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 0 deletions.
23 changes: 23 additions & 0 deletions bigquery/integration_test.go
Expand Up @@ -2197,6 +2197,29 @@ func TestIntegration_LegacyQuery(t *testing.T) {
}
}

func TestIntegration_IteratorSource(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
}
ctx := context.Background()
q := client.Query("SELECT 17 as foo")
it, err := q.Read(ctx)
if err != nil {
t.Errorf("Read: %v", err)
}
src := it.SourceJob()
if src == nil {
t.Errorf("wanted source job, got nil")
}
status, err := src.Status(ctx)
if err != nil {
t.Errorf("Status: %v", err)
}
if status == nil {
t.Errorf("got nil status")
}
}

func TestIntegration_QueryExternalHivePartitioning(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
Expand Down
17 changes: 17 additions & 0 deletions bigquery/iterator.go
Expand Up @@ -63,6 +63,23 @@ type RowIterator struct {
structLoader structLoader // used to populate a pointer to a struct
}

// SourceJob returns an instance of a Job if the RowIterator is backed by a query,
// or a nil.
func (ri *RowIterator) SourceJob() *Job {
if ri.src == nil {
return nil
}
if ri.src.j == nil {
return nil
}
return &Job{
c: ri.src.j.c,
projectID: ri.src.j.projectID,
location: ri.src.j.location,
jobID: ri.src.j.jobID,
}
}

// We declare a function signature for fetching results. The primary reason
// for this is to enable us to swap out the fetch function with alternate
// implementations (e.g. to enable testing).
Expand Down
39 changes: 39 additions & 0 deletions bigquery/iterator_test.go
Expand Up @@ -471,3 +471,42 @@ func TestIteratorNextTypes(t *testing.T) {
}
}
}

func TestIteratorSourceJob(t *testing.T) {
testcases := []struct {
description string
src *rowSource
wantJob *Job
}{
{
description: "nil source",
src: nil,
wantJob: nil,
},
{
description: "empty source",
src: &rowSource{},
wantJob: nil,
},
{
description: "table source",
src: &rowSource{t: &Table{ProjectID: "p", DatasetID: "d", TableID: "t"}},
wantJob: nil,
},
{
description: "job source",
src: &rowSource{j: &Job{projectID: "p", location: "l", jobID: "j"}},
wantJob: &Job{projectID: "p", location: "l", jobID: "j"},
},
}

for _, tc := range testcases {
// Don't pass a page func, we're not reading from the iterator.
it := newRowIterator(context.Background(), tc.src, nil)
got := it.SourceJob()
// AllowUnexported because of the embedded client reference, which we're ignoring.
if !cmp.Equal(got, tc.wantJob, cmp.AllowUnexported(Job{})) {
t.Errorf("%s: mismatch on SourceJob, got %v want %v", tc.description, got, tc.wantJob)
}
}
}

0 comments on commit 868d07d

Please sign in to comment.