diff --git a/bigquery/integration_test.go b/bigquery/integration_test.go index e450e207ec5..94d98f1401d 100644 --- a/bigquery/integration_test.go +++ b/bigquery/integration_test.go @@ -1630,6 +1630,63 @@ func TestIntegration_TableUpdate(t *testing.T) { } } +func TestIntegration_QueryStatistics(t *testing.T) { + // Make a bunch of assertions on a simple query. + if client == nil { + t.Skip("Integration tests skipped") + } + ctx := context.Background() + + q := client.Query("SELECT 17 as foo, 3.14 as bar") + // disable cache to ensure we have query statistics + q.DisableQueryCache = true + + job, err := q.Run(ctx) + if err != nil { + t.Fatalf("job Run failure: %v", err) + } + status, err := job.Wait(ctx) + if err != nil { + t.Fatalf("job Wait failure: %v", err) + } + if status.Statistics == nil { + t.Fatal("expected job statistics, none found") + } + + if status.Statistics.NumChildJobs != 0 { + t.Errorf("expected no children, %d reported", status.Statistics.NumChildJobs) + } + + if status.Statistics.ParentJobID != "" { + t.Errorf("expected no parent, but parent present: %s", status.Statistics.ParentJobID) + } + + if status.Statistics.Details == nil { + t.Fatal("expected job details, none present") + } + + qStats, ok := status.Statistics.Details.(*QueryStatistics) + if !ok { + t.Fatalf("expected query statistics not present") + } + + if qStats.CacheHit { + t.Error("unexpected cache hit") + } + + if qStats.StatementType != "SELECT" { + t.Errorf("expected SELECT statement type, got: %s", qStats.StatementType) + } + + if len(qStats.QueryPlan) == 0 { + t.Error("expected query plan, none present") + } + + if len(qStats.Timeline) == 0 { + t.Error("expected query timeline, none present") + } +} + func TestIntegration_Load(t *testing.T) { if client == nil { t.Skip("Integration tests skipped") diff --git a/bigquery/job.go b/bigquery/job.go index 6e0745d8d55..1e08e899c51 100644 --- a/bigquery/job.go +++ b/bigquery/job.go @@ -347,6 +347,9 @@ type JobStatistics struct { // ScriptStatistics includes information run as part of a child job within // a script. ScriptStatistics *ScriptStatistics + + // ReservationUsage attributes slot consumption to reservations. + ReservationUsage []*ReservationUsage } // Statistics is one of ExtractStatistics, LoadStatistics or QueryStatistics. @@ -561,6 +564,25 @@ type QueryTimelineSample struct { SlotMillis int64 } +// ReservationUsage contains information about a job's usage of a single reservation. +type ReservationUsage struct { + // SlotMillis reports the slot milliseconds utilized within in the given reservation. + SlotMillis int64 + // Name indicates the utilized reservation name, or "unreserved" for ondemand usage. + Name string +} + +func bqToReservationUsage(ru []*bq.JobStatisticsReservationUsage) []*ReservationUsage { + var usage []*ReservationUsage + for _, in := range ru { + usage = append(usage, &ReservationUsage{ + SlotMillis: in.SlotMs, + Name: in.Name, + }) + } + return usage +} + // ScriptStatistics report information about script-based query jobs. type ScriptStatistics struct { EvaluationKind string @@ -810,6 +832,7 @@ func (j *Job) setStatistics(s *bq.JobStatistics, c *Client) { NumChildJobs: s.NumChildJobs, ParentJobID: s.ParentJobId, ScriptStatistics: bqToScriptStatistics(s.ScriptStatistics), + ReservationUsage: bqToReservationUsage(s.ReservationUsage), } switch { case s.Extract != nil: