You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

831 lines
25 KiB

  1. // Copyright 2015 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package bigquery
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "time"
  20. "cloud.google.com/go/internal"
  21. "cloud.google.com/go/internal/trace"
  22. gax "github.com/googleapis/gax-go/v2"
  23. bq "google.golang.org/api/bigquery/v2"
  24. "google.golang.org/api/googleapi"
  25. "google.golang.org/api/iterator"
  26. )
  27. // A Job represents an operation which has been submitted to BigQuery for processing.
  28. type Job struct {
  29. c *Client
  30. projectID string
  31. jobID string
  32. location string
  33. email string
  34. config *bq.JobConfiguration
  35. lastStatus *JobStatus
  36. }
  37. // JobFromID creates a Job which refers to an existing BigQuery job. The job
  38. // need not have been created by this package. For example, the job may have
  39. // been created in the BigQuery console.
  40. //
  41. // For jobs whose location is other than "US" or "EU", set Client.Location or use
  42. // JobFromIDLocation.
  43. func (c *Client) JobFromID(ctx context.Context, id string) (*Job, error) {
  44. return c.JobFromIDLocation(ctx, id, c.Location)
  45. }
  46. // JobFromIDLocation creates a Job which refers to an existing BigQuery job. The job
  47. // need not have been created by this package (for example, it may have
  48. // been created in the BigQuery console), but it must exist in the specified location.
  49. func (c *Client) JobFromIDLocation(ctx context.Context, id, location string) (j *Job, err error) {
  50. ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.JobFromIDLocation")
  51. defer func() { trace.EndSpan(ctx, err) }()
  52. bqjob, err := c.getJobInternal(ctx, id, location, "configuration", "jobReference", "status", "statistics")
  53. if err != nil {
  54. return nil, err
  55. }
  56. return bqToJob(bqjob, c)
  57. }
  58. // ID returns the job's ID.
  59. func (j *Job) ID() string {
  60. return j.jobID
  61. }
  62. // Location returns the job's location.
  63. func (j *Job) Location() string {
  64. return j.location
  65. }
  66. // Email returns the email of the job's creator.
  67. func (j *Job) Email() string {
  68. return j.email
  69. }
  70. // State is one of a sequence of states that a Job progresses through as it is processed.
  71. type State int
  72. const (
  73. // StateUnspecified is the default JobIterator state.
  74. StateUnspecified State = iota
  75. // Pending is a state that describes that the job is pending.
  76. Pending
  77. // Running is a state that describes that the job is running.
  78. Running
  79. // Done is a state that describes that the job is done.
  80. Done
  81. )
  82. // JobStatus contains the current State of a job, and errors encountered while processing that job.
  83. type JobStatus struct {
  84. State State
  85. err error
  86. // All errors encountered during the running of the job.
  87. // Not all Errors are fatal, so errors here do not necessarily mean that the job has completed or was unsuccessful.
  88. Errors []*Error
  89. // Statistics about the job.
  90. Statistics *JobStatistics
  91. }
  92. // JobConfig contains configuration information for a job. It is implemented by
  93. // *CopyConfig, *ExtractConfig, *LoadConfig and *QueryConfig.
  94. type JobConfig interface {
  95. isJobConfig()
  96. }
  97. func (*CopyConfig) isJobConfig() {}
  98. func (*ExtractConfig) isJobConfig() {}
  99. func (*LoadConfig) isJobConfig() {}
  100. func (*QueryConfig) isJobConfig() {}
  101. // Config returns the configuration information for j.
  102. func (j *Job) Config() (JobConfig, error) {
  103. return bqToJobConfig(j.config, j.c)
  104. }
  105. func bqToJobConfig(q *bq.JobConfiguration, c *Client) (JobConfig, error) {
  106. switch {
  107. case q == nil:
  108. return nil, nil
  109. case q.Copy != nil:
  110. return bqToCopyConfig(q, c), nil
  111. case q.Extract != nil:
  112. return bqToExtractConfig(q, c), nil
  113. case q.Load != nil:
  114. return bqToLoadConfig(q, c), nil
  115. case q.Query != nil:
  116. return bqToQueryConfig(q, c)
  117. default:
  118. return nil, nil
  119. }
  120. }
  121. // JobIDConfig describes how to create an ID for a job.
  122. type JobIDConfig struct {
  123. // JobID is the ID to use for the job. If empty, a random job ID will be generated.
  124. JobID string
  125. // If AddJobIDSuffix is true, then a random string will be appended to JobID.
  126. AddJobIDSuffix bool
  127. // Location is the location for the job.
  128. Location string
  129. }
  130. // createJobRef creates a JobReference.
  131. func (j *JobIDConfig) createJobRef(c *Client) *bq.JobReference {
  132. // We don't check whether projectID is empty; the server will return an
  133. // error when it encounters the resulting JobReference.
  134. loc := j.Location
  135. if loc == "" { // Use Client.Location as a default.
  136. loc = c.Location
  137. }
  138. jr := &bq.JobReference{ProjectId: c.projectID, Location: loc}
  139. if j.JobID == "" {
  140. jr.JobId = randomIDFn()
  141. } else if j.AddJobIDSuffix {
  142. jr.JobId = j.JobID + "-" + randomIDFn()
  143. } else {
  144. jr.JobId = j.JobID
  145. }
  146. return jr
  147. }
  148. // Done reports whether the job has completed.
  149. // After Done returns true, the Err method will return an error if the job completed unsuccessfully.
  150. func (s *JobStatus) Done() bool {
  151. return s.State == Done
  152. }
  153. // Err returns the error that caused the job to complete unsuccessfully (if any).
  154. func (s *JobStatus) Err() error {
  155. return s.err
  156. }
  157. // Status retrieves the current status of the job from BigQuery. It fails if the Status could not be determined.
  158. func (j *Job) Status(ctx context.Context) (js *JobStatus, err error) {
  159. ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Job.Status")
  160. defer func() { trace.EndSpan(ctx, err) }()
  161. bqjob, err := j.c.getJobInternal(ctx, j.jobID, j.location, "status", "statistics")
  162. if err != nil {
  163. return nil, err
  164. }
  165. if err := j.setStatus(bqjob.Status); err != nil {
  166. return nil, err
  167. }
  168. j.setStatistics(bqjob.Statistics, j.c)
  169. return j.lastStatus, nil
  170. }
  171. // LastStatus returns the most recently retrieved status of the job. The status is
  172. // retrieved when a new job is created, or when JobFromID or Job.Status is called.
  173. // Call Job.Status to get the most up-to-date information about a job.
  174. func (j *Job) LastStatus() *JobStatus {
  175. return j.lastStatus
  176. }
  177. // Cancel requests that a job be cancelled. This method returns without waiting for
  178. // cancellation to take effect. To check whether the job has terminated, use Job.Status.
  179. // Cancelled jobs may still incur costs.
  180. func (j *Job) Cancel(ctx context.Context) error {
  181. // Jobs.Cancel returns a job entity, but the only relevant piece of
  182. // data it may contain (the status of the job) is unreliable. From the
  183. // docs: "This call will return immediately, and the client will need
  184. // to poll for the job status to see if the cancel completed
  185. // successfully". So it would be misleading to return a status.
  186. call := j.c.bqs.Jobs.Cancel(j.projectID, j.jobID).
  187. Location(j.location).
  188. Fields(). // We don't need any of the response data.
  189. Context(ctx)
  190. setClientHeader(call.Header())
  191. return runWithRetry(ctx, func() error {
  192. _, err := call.Do()
  193. return err
  194. })
  195. }
  196. // Wait blocks until the job or the context is done. It returns the final status
  197. // of the job.
  198. // If an error occurs while retrieving the status, Wait returns that error. But
  199. // Wait returns nil if the status was retrieved successfully, even if
  200. // status.Err() != nil. So callers must check both errors. See the example.
  201. func (j *Job) Wait(ctx context.Context) (js *JobStatus, err error) {
  202. ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Job.Wait")
  203. defer func() { trace.EndSpan(ctx, err) }()
  204. if j.isQuery() {
  205. // We can avoid polling for query jobs.
  206. if _, _, err := j.waitForQuery(ctx, j.projectID); err != nil {
  207. return nil, err
  208. }
  209. // Note: extra RPC even if you just want to wait for the query to finish.
  210. js, err := j.Status(ctx)
  211. if err != nil {
  212. return nil, err
  213. }
  214. return js, nil
  215. }
  216. // Non-query jobs must poll.
  217. err = internal.Retry(ctx, gax.Backoff{}, func() (stop bool, err error) {
  218. js, err = j.Status(ctx)
  219. if err != nil {
  220. return true, err
  221. }
  222. if js.Done() {
  223. return true, nil
  224. }
  225. return false, nil
  226. })
  227. if err != nil {
  228. return nil, err
  229. }
  230. return js, nil
  231. }
  232. // Read fetches the results of a query job.
  233. // If j is not a query job, Read returns an error.
  234. func (j *Job) Read(ctx context.Context) (ri *RowIterator, err error) {
  235. ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Job.Read")
  236. defer func() { trace.EndSpan(ctx, err) }()
  237. return j.read(ctx, j.waitForQuery, fetchPage)
  238. }
  239. func (j *Job) read(ctx context.Context, waitForQuery func(context.Context, string) (Schema, uint64, error), pf pageFetcher) (*RowIterator, error) {
  240. if !j.isQuery() {
  241. return nil, errors.New("bigquery: cannot read from a non-query job")
  242. }
  243. destTable := j.config.Query.DestinationTable
  244. // The destination table should only be nil if there was a query error.
  245. projectID := j.projectID
  246. if destTable != nil && projectID != destTable.ProjectId {
  247. return nil, fmt.Errorf("bigquery: job project ID is %q, but destination table's is %q", projectID, destTable.ProjectId)
  248. }
  249. schema, totalRows, err := waitForQuery(ctx, projectID)
  250. if err != nil {
  251. return nil, err
  252. }
  253. if destTable == nil {
  254. return nil, errors.New("bigquery: query job missing destination table")
  255. }
  256. dt := bqToTable(destTable, j.c)
  257. if totalRows == 0 {
  258. pf = nil
  259. }
  260. it := newRowIterator(ctx, dt, pf)
  261. it.Schema = schema
  262. it.TotalRows = totalRows
  263. return it, nil
  264. }
  265. // waitForQuery waits for the query job to complete and returns its schema. It also
  266. // returns the total number of rows in the result set.
  267. func (j *Job) waitForQuery(ctx context.Context, projectID string) (Schema, uint64, error) {
  268. // Use GetQueryResults only to wait for completion, not to read results.
  269. call := j.c.bqs.Jobs.GetQueryResults(projectID, j.jobID).Location(j.location).Context(ctx).MaxResults(0)
  270. setClientHeader(call.Header())
  271. backoff := gax.Backoff{
  272. Initial: 1 * time.Second,
  273. Multiplier: 2,
  274. Max: 60 * time.Second,
  275. }
  276. var res *bq.GetQueryResultsResponse
  277. err := internal.Retry(ctx, backoff, func() (stop bool, err error) {
  278. res, err = call.Do()
  279. if err != nil {
  280. return !retryableError(err), err
  281. }
  282. if !res.JobComplete { // GetQueryResults may return early without error; retry.
  283. return false, nil
  284. }
  285. return true, nil
  286. })
  287. if err != nil {
  288. return nil, 0, err
  289. }
  290. return bqToSchema(res.Schema), res.TotalRows, nil
  291. }
  292. // JobStatistics contains statistics about a job.
  293. type JobStatistics struct {
  294. CreationTime time.Time
  295. StartTime time.Time
  296. EndTime time.Time
  297. TotalBytesProcessed int64
  298. Details Statistics
  299. }
  300. // Statistics is one of ExtractStatistics, LoadStatistics or QueryStatistics.
  301. type Statistics interface {
  302. implementsStatistics()
  303. }
  304. // ExtractStatistics contains statistics about an extract job.
  305. type ExtractStatistics struct {
  306. // The number of files per destination URI or URI pattern specified in the
  307. // extract configuration. These values will be in the same order as the
  308. // URIs specified in the 'destinationUris' field.
  309. DestinationURIFileCounts []int64
  310. }
  311. // LoadStatistics contains statistics about a load job.
  312. type LoadStatistics struct {
  313. // The number of bytes of source data in a load job.
  314. InputFileBytes int64
  315. // The number of source files in a load job.
  316. InputFiles int64
  317. // Size of the loaded data in bytes. Note that while a load job is in the
  318. // running state, this value may change.
  319. OutputBytes int64
  320. // The number of rows imported in a load job. Note that while an import job is
  321. // in the running state, this value may change.
  322. OutputRows int64
  323. }
  324. // QueryStatistics contains statistics about a query job.
  325. type QueryStatistics struct {
  326. // Billing tier for the job.
  327. BillingTier int64
  328. // Whether the query result was fetched from the query cache.
  329. CacheHit bool
  330. // The type of query statement, if valid.
  331. StatementType string
  332. // Total bytes billed for the job.
  333. TotalBytesBilled int64
  334. // Total bytes processed for the job.
  335. TotalBytesProcessed int64
  336. // For dry run queries, indicates how accurate the TotalBytesProcessed value is.
  337. // When indicated, values include:
  338. // UNKNOWN: accuracy of the estimate is unknown.
  339. // PRECISE: estimate is precise.
  340. // LOWER_BOUND: estimate is lower bound of what the query would cost.
  341. // UPPER_BOUND: estiamte is upper bound of what the query would cost.
  342. TotalBytesProcessedAccuracy string
  343. // Describes execution plan for the query.
  344. QueryPlan []*ExplainQueryStage
  345. // The number of rows affected by a DML statement. Present only for DML
  346. // statements INSERT, UPDATE or DELETE.
  347. NumDMLAffectedRows int64
  348. // Describes a timeline of job execution.
  349. Timeline []*QueryTimelineSample
  350. // ReferencedTables: [Output-only, Experimental] Referenced tables for
  351. // the job. Queries that reference more than 50 tables will not have a
  352. // complete list.
  353. ReferencedTables []*Table
  354. // The schema of the results. Present only for successful dry run of
  355. // non-legacy SQL queries.
  356. Schema Schema
  357. // Slot-milliseconds consumed by this query job.
  358. SlotMillis int64
  359. // Standard SQL: list of undeclared query parameter names detected during a
  360. // dry run validation.
  361. UndeclaredQueryParameterNames []string
  362. // DDL target table.
  363. DDLTargetTable *Table
  364. // DDL Operation performed on the target table. Used to report how the
  365. // query impacted the DDL target table.
  366. DDLOperationPerformed string
  367. }
  368. // ExplainQueryStage describes one stage of a query.
  369. type ExplainQueryStage struct {
  370. // CompletedParallelInputs: Number of parallel input segments completed.
  371. CompletedParallelInputs int64
  372. // ComputeAvg: Duration the average shard spent on CPU-bound tasks.
  373. ComputeAvg time.Duration
  374. // ComputeMax: Duration the slowest shard spent on CPU-bound tasks.
  375. ComputeMax time.Duration
  376. // Relative amount of the total time the average shard spent on CPU-bound tasks.
  377. ComputeRatioAvg float64
  378. // Relative amount of the total time the slowest shard spent on CPU-bound tasks.
  379. ComputeRatioMax float64
  380. // EndTime: Stage end time.
  381. EndTime time.Time
  382. // Unique ID for stage within plan.
  383. ID int64
  384. // InputStages: IDs for stages that are inputs to this stage.
  385. InputStages []int64
  386. // Human-readable name for stage.
  387. Name string
  388. // ParallelInputs: Number of parallel input segments to be processed.
  389. ParallelInputs int64
  390. // ReadAvg: Duration the average shard spent reading input.
  391. ReadAvg time.Duration
  392. // ReadMax: Duration the slowest shard spent reading input.
  393. ReadMax time.Duration
  394. // Relative amount of the total time the average shard spent reading input.
  395. ReadRatioAvg float64
  396. // Relative amount of the total time the slowest shard spent reading input.
  397. ReadRatioMax float64
  398. // Number of records read into the stage.
  399. RecordsRead int64
  400. // Number of records written by the stage.
  401. RecordsWritten int64
  402. // ShuffleOutputBytes: Total number of bytes written to shuffle.
  403. ShuffleOutputBytes int64
  404. // ShuffleOutputBytesSpilled: Total number of bytes written to shuffle
  405. // and spilled to disk.
  406. ShuffleOutputBytesSpilled int64
  407. // StartTime: Stage start time.
  408. StartTime time.Time
  409. // Current status for the stage.
  410. Status string
  411. // List of operations within the stage in dependency order (approximately
  412. // chronological).
  413. Steps []*ExplainQueryStep
  414. // WaitAvg: Duration the average shard spent waiting to be scheduled.
  415. WaitAvg time.Duration
  416. // WaitMax: Duration the slowest shard spent waiting to be scheduled.
  417. WaitMax time.Duration
  418. // Relative amount of the total time the average shard spent waiting to be scheduled.
  419. WaitRatioAvg float64
  420. // Relative amount of the total time the slowest shard spent waiting to be scheduled.
  421. WaitRatioMax float64
  422. // WriteAvg: Duration the average shard spent on writing output.
  423. WriteAvg time.Duration
  424. // WriteMax: Duration the slowest shard spent on writing output.
  425. WriteMax time.Duration
  426. // Relative amount of the total time the average shard spent on writing output.
  427. WriteRatioAvg float64
  428. // Relative amount of the total time the slowest shard spent on writing output.
  429. WriteRatioMax float64
  430. }
  431. // ExplainQueryStep describes one step of a query stage.
  432. type ExplainQueryStep struct {
  433. // Machine-readable operation type.
  434. Kind string
  435. // Human-readable stage descriptions.
  436. Substeps []string
  437. }
  438. // QueryTimelineSample represents a sample of execution statistics at a point in time.
  439. type QueryTimelineSample struct {
  440. // Total number of units currently being processed by workers, represented as largest value since last sample.
  441. ActiveUnits int64
  442. // Total parallel units of work completed by this query.
  443. CompletedUnits int64
  444. // Time elapsed since start of query execution.
  445. Elapsed time.Duration
  446. // Total parallel units of work remaining for the active stages.
  447. PendingUnits int64
  448. // Cumulative slot-milliseconds consumed by the query.
  449. SlotMillis int64
  450. }
  451. func (*ExtractStatistics) implementsStatistics() {}
  452. func (*LoadStatistics) implementsStatistics() {}
  453. func (*QueryStatistics) implementsStatistics() {}
  454. // Jobs lists jobs within a project.
  455. func (c *Client) Jobs(ctx context.Context) *JobIterator {
  456. it := &JobIterator{
  457. ctx: ctx,
  458. c: c,
  459. ProjectID: c.projectID,
  460. }
  461. it.pageInfo, it.nextFunc = iterator.NewPageInfo(
  462. it.fetch,
  463. func() int { return len(it.items) },
  464. func() interface{} { b := it.items; it.items = nil; return b })
  465. return it
  466. }
  467. // JobIterator iterates over jobs in a project.
  468. type JobIterator struct {
  469. ProjectID string // Project ID of the jobs to list. Default is the client's project.
  470. AllUsers bool // Whether to list jobs owned by all users in the project, or just the current caller.
  471. State State // List only jobs in the given state. Defaults to all states.
  472. MinCreationTime time.Time // List only jobs created after this time.
  473. MaxCreationTime time.Time // List only jobs created before this time.
  474. ctx context.Context
  475. c *Client
  476. pageInfo *iterator.PageInfo
  477. nextFunc func() error
  478. items []*Job
  479. }
  480. // PageInfo is a getter for the JobIterator's PageInfo.
  481. func (it *JobIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
  482. // Next returns the next Job. Its second return value is iterator.Done if
  483. // there are no more results. Once Next returns Done, all subsequent calls will
  484. // return Done.
  485. func (it *JobIterator) Next() (*Job, error) {
  486. if err := it.nextFunc(); err != nil {
  487. return nil, err
  488. }
  489. item := it.items[0]
  490. it.items = it.items[1:]
  491. return item, nil
  492. }
  493. func (it *JobIterator) fetch(pageSize int, pageToken string) (string, error) {
  494. var st string
  495. switch it.State {
  496. case StateUnspecified:
  497. st = ""
  498. case Pending:
  499. st = "pending"
  500. case Running:
  501. st = "running"
  502. case Done:
  503. st = "done"
  504. default:
  505. return "", fmt.Errorf("bigquery: invalid value for JobIterator.State: %d", it.State)
  506. }
  507. req := it.c.bqs.Jobs.List(it.ProjectID).
  508. Context(it.ctx).
  509. PageToken(pageToken).
  510. Projection("full").
  511. AllUsers(it.AllUsers)
  512. if st != "" {
  513. req.StateFilter(st)
  514. }
  515. if !it.MinCreationTime.IsZero() {
  516. req.MinCreationTime(uint64(it.MinCreationTime.UnixNano() / 1e6))
  517. }
  518. if !it.MaxCreationTime.IsZero() {
  519. req.MaxCreationTime(uint64(it.MaxCreationTime.UnixNano() / 1e6))
  520. }
  521. setClientHeader(req.Header())
  522. if pageSize > 0 {
  523. req.MaxResults(int64(pageSize))
  524. }
  525. res, err := req.Do()
  526. if err != nil {
  527. return "", err
  528. }
  529. for _, j := range res.Jobs {
  530. job, err := convertListedJob(j, it.c)
  531. if err != nil {
  532. return "", err
  533. }
  534. it.items = append(it.items, job)
  535. }
  536. return res.NextPageToken, nil
  537. }
  538. func convertListedJob(j *bq.JobListJobs, c *Client) (*Job, error) {
  539. return bqToJob2(j.JobReference, j.Configuration, j.Status, j.Statistics, j.UserEmail, c)
  540. }
  541. func (c *Client) getJobInternal(ctx context.Context, jobID, location string, fields ...googleapi.Field) (*bq.Job, error) {
  542. var job *bq.Job
  543. call := c.bqs.Jobs.Get(c.projectID, jobID).Context(ctx)
  544. if location != "" {
  545. call = call.Location(location)
  546. }
  547. if len(fields) > 0 {
  548. call = call.Fields(fields...)
  549. }
  550. setClientHeader(call.Header())
  551. err := runWithRetry(ctx, func() (err error) {
  552. job, err = call.Do()
  553. return err
  554. })
  555. if err != nil {
  556. return nil, err
  557. }
  558. return job, nil
  559. }
  560. func bqToJob(q *bq.Job, c *Client) (*Job, error) {
  561. return bqToJob2(q.JobReference, q.Configuration, q.Status, q.Statistics, q.UserEmail, c)
  562. }
  563. func bqToJob2(qr *bq.JobReference, qc *bq.JobConfiguration, qs *bq.JobStatus, qt *bq.JobStatistics, email string, c *Client) (*Job, error) {
  564. j := &Job{
  565. projectID: qr.ProjectId,
  566. jobID: qr.JobId,
  567. location: qr.Location,
  568. c: c,
  569. email: email,
  570. }
  571. j.setConfig(qc)
  572. if err := j.setStatus(qs); err != nil {
  573. return nil, err
  574. }
  575. j.setStatistics(qt, c)
  576. return j, nil
  577. }
  578. func (j *Job) setConfig(config *bq.JobConfiguration) {
  579. if config == nil {
  580. return
  581. }
  582. j.config = config
  583. }
  584. func (j *Job) isQuery() bool {
  585. return j.config != nil && j.config.Query != nil
  586. }
  587. var stateMap = map[string]State{"PENDING": Pending, "RUNNING": Running, "DONE": Done}
  588. func (j *Job) setStatus(qs *bq.JobStatus) error {
  589. if qs == nil {
  590. return nil
  591. }
  592. state, ok := stateMap[qs.State]
  593. if !ok {
  594. return fmt.Errorf("unexpected job state: %v", qs.State)
  595. }
  596. j.lastStatus = &JobStatus{
  597. State: state,
  598. err: nil,
  599. }
  600. if err := bqToError(qs.ErrorResult); state == Done && err != nil {
  601. j.lastStatus.err = err
  602. }
  603. for _, ep := range qs.Errors {
  604. j.lastStatus.Errors = append(j.lastStatus.Errors, bqToError(ep))
  605. }
  606. return nil
  607. }
  608. func (j *Job) setStatistics(s *bq.JobStatistics, c *Client) {
  609. if s == nil || j.lastStatus == nil {
  610. return
  611. }
  612. js := &JobStatistics{
  613. CreationTime: unixMillisToTime(s.CreationTime),
  614. StartTime: unixMillisToTime(s.StartTime),
  615. EndTime: unixMillisToTime(s.EndTime),
  616. TotalBytesProcessed: s.TotalBytesProcessed,
  617. }
  618. switch {
  619. case s.Extract != nil:
  620. js.Details = &ExtractStatistics{
  621. DestinationURIFileCounts: []int64(s.Extract.DestinationUriFileCounts),
  622. }
  623. case s.Load != nil:
  624. js.Details = &LoadStatistics{
  625. InputFileBytes: s.Load.InputFileBytes,
  626. InputFiles: s.Load.InputFiles,
  627. OutputBytes: s.Load.OutputBytes,
  628. OutputRows: s.Load.OutputRows,
  629. }
  630. case s.Query != nil:
  631. var names []string
  632. for _, qp := range s.Query.UndeclaredQueryParameters {
  633. names = append(names, qp.Name)
  634. }
  635. var tables []*Table
  636. for _, tr := range s.Query.ReferencedTables {
  637. tables = append(tables, bqToTable(tr, c))
  638. }
  639. js.Details = &QueryStatistics{
  640. BillingTier: s.Query.BillingTier,
  641. CacheHit: s.Query.CacheHit,
  642. DDLTargetTable: bqToTable(s.Query.DdlTargetTable, c),
  643. DDLOperationPerformed: s.Query.DdlOperationPerformed,
  644. StatementType: s.Query.StatementType,
  645. TotalBytesBilled: s.Query.TotalBytesBilled,
  646. TotalBytesProcessed: s.Query.TotalBytesProcessed,
  647. TotalBytesProcessedAccuracy: s.Query.TotalBytesProcessedAccuracy,
  648. NumDMLAffectedRows: s.Query.NumDmlAffectedRows,
  649. QueryPlan: queryPlanFromProto(s.Query.QueryPlan),
  650. Schema: bqToSchema(s.Query.Schema),
  651. SlotMillis: s.Query.TotalSlotMs,
  652. Timeline: timelineFromProto(s.Query.Timeline),
  653. ReferencedTables: tables,
  654. UndeclaredQueryParameterNames: names,
  655. }
  656. }
  657. j.lastStatus.Statistics = js
  658. }
  659. func queryPlanFromProto(stages []*bq.ExplainQueryStage) []*ExplainQueryStage {
  660. var res []*ExplainQueryStage
  661. for _, s := range stages {
  662. var steps []*ExplainQueryStep
  663. for _, p := range s.Steps {
  664. steps = append(steps, &ExplainQueryStep{
  665. Kind: p.Kind,
  666. Substeps: p.Substeps,
  667. })
  668. }
  669. res = append(res, &ExplainQueryStage{
  670. CompletedParallelInputs: s.CompletedParallelInputs,
  671. ComputeAvg: time.Duration(s.ComputeMsAvg) * time.Millisecond,
  672. ComputeMax: time.Duration(s.ComputeMsMax) * time.Millisecond,
  673. ComputeRatioAvg: s.ComputeRatioAvg,
  674. ComputeRatioMax: s.ComputeRatioMax,
  675. EndTime: time.Unix(0, s.EndMs*1e6),
  676. ID: s.Id,
  677. InputStages: s.InputStages,
  678. Name: s.Name,
  679. ParallelInputs: s.ParallelInputs,
  680. ReadAvg: time.Duration(s.ReadMsAvg) * time.Millisecond,
  681. ReadMax: time.Duration(s.ReadMsMax) * time.Millisecond,
  682. ReadRatioAvg: s.ReadRatioAvg,
  683. ReadRatioMax: s.ReadRatioMax,
  684. RecordsRead: s.RecordsRead,
  685. RecordsWritten: s.RecordsWritten,
  686. ShuffleOutputBytes: s.ShuffleOutputBytes,
  687. ShuffleOutputBytesSpilled: s.ShuffleOutputBytesSpilled,
  688. StartTime: time.Unix(0, s.StartMs*1e6),
  689. Status: s.Status,
  690. Steps: steps,
  691. WaitAvg: time.Duration(s.WaitMsAvg) * time.Millisecond,
  692. WaitMax: time.Duration(s.WaitMsMax) * time.Millisecond,
  693. WaitRatioAvg: s.WaitRatioAvg,
  694. WaitRatioMax: s.WaitRatioMax,
  695. WriteAvg: time.Duration(s.WriteMsAvg) * time.Millisecond,
  696. WriteMax: time.Duration(s.WriteMsMax) * time.Millisecond,
  697. WriteRatioAvg: s.WriteRatioAvg,
  698. WriteRatioMax: s.WriteRatioMax,
  699. })
  700. }
  701. return res
  702. }
  703. func timelineFromProto(timeline []*bq.QueryTimelineSample) []*QueryTimelineSample {
  704. var res []*QueryTimelineSample
  705. for _, s := range timeline {
  706. res = append(res, &QueryTimelineSample{
  707. ActiveUnits: s.ActiveUnits,
  708. CompletedUnits: s.CompletedUnits,
  709. Elapsed: time.Duration(s.ElapsedMs) * time.Millisecond,
  710. PendingUnits: s.PendingUnits,
  711. SlotMillis: s.TotalSlotMs,
  712. })
  713. }
  714. return res
  715. }