1 | ;
|
2 | /*!
|
3 | * Copyright 2014 Google Inc. All Rights Reserved.
|
4 | *
|
5 | * Licensed under the Apache License, Version 2.0 (the "License");
|
6 | * you may not use this file except in compliance with the License.
|
7 | * You may obtain a copy of the License at
|
8 | *
|
9 | * http://www.apache.org/licenses/LICENSE-2.0
|
10 | *
|
11 | * Unless required by applicable law or agreed to in writing, software
|
12 | * distributed under the License is distributed on an "AS IS" BASIS,
|
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14 | * See the License for the specific language governing permissions and
|
15 | * limitations under the License.
|
16 | */
|
17 | Object.defineProperty(exports, "__esModule", { value: true });
|
18 | exports.Job = void 0;
|
19 | /*!
|
20 | * @module bigquery/job
|
21 | */
|
22 | const common_1 = require("@google-cloud/common");
|
23 | const paginator_1 = require("@google-cloud/paginator");
|
24 | const promisify_1 = require("@google-cloud/promisify");
|
25 | const extend = require("extend");
|
26 | const bigquery_1 = require("./bigquery");
|
27 | const logger_1 = require("./logger");
|
28 | /**
|
29 | * @callback QueryResultsCallback
|
30 | * @param {?Error} err An error returned while making this request.
|
31 | * @param {array} rows The results of the job.
|
32 | */
|
33 | /**
|
34 | * @callback ManualQueryResultsCallback
|
35 | * @param {?Error} err An error returned while making this request.
|
36 | * @param {array} rows The results of the job.
|
37 | * @param {?object} nextQuery A pre-made configuration object for your next
|
38 | * request. This will be `null` if no additional results are available.
|
39 | * If the query is not yet complete, you may get empty `rows` and
|
40 | * non-`null` `nextQuery` that you should use for your next request.
|
41 | * @param {object} apiResponse The full API response.
|
42 | */
|
43 | /**
|
44 | * Job objects are returned from various places in the BigQuery API:
|
45 | *
|
46 | * - {@link BigQuery#getJobs}
|
47 | * - {@link BigQuery#job}
|
48 | * - {@link BigQuery#query}
|
49 | * - {@link BigQuery#createJob}
|
50 | * - {@link Table#copy}
|
51 | * - {@link Table#createWriteStream}
|
52 | * - {@link Table#extract}
|
53 | * - {@link Table#load}
|
54 | *
|
55 | * They can be used to check the status of a running job or fetching the results
|
56 | * of a previously-executed one.
|
57 | *
|
58 | * @class
|
59 | * @param {BigQuery} bigQuery {@link BigQuery} instance.
|
60 | * @param {string} id The ID of the job.
|
61 | * @param {object} [options] Configuration object.
|
62 | * @param {string} [options.location] The geographic location of the job.
|
63 | * Required except for US and EU.
|
64 | *
|
65 | * @example
|
66 | * ```
|
67 | * const {BigQuery} = require('@google-cloud/bigquery');
|
68 | * const bigquery = new BigQuery();
|
69 | *
|
70 | * const job = bigquery.job('job-id');
|
71 | *
|
72 | * //-
|
73 | * // All jobs are event emitters. The status of each job is polled
|
74 | * // continuously, starting only after you register a "complete" listener.
|
75 | * //-
|
76 | * job.on('complete', (metadata) => {
|
77 | * // The job is complete.
|
78 | * });
|
79 | *
|
80 | * //-
|
81 | * // Be sure to register an error handler as well to catch any issues which
|
82 | * // impeded the job.
|
83 | * //-
|
84 | * job.on('error', (err) => {
|
85 | * // An error occurred during the job.
|
86 | * });
|
87 | *
|
88 | * //-
|
89 | * // To force the Job object to stop polling for updates, simply remove any
|
90 | * // "complete" listeners you've registered.
|
91 | * //
|
92 | * // The easiest way to do this is with `removeAllListeners()`.
|
93 | * //-
|
94 | * job.removeAllListeners();
|
95 | * ```
|
96 | */
|
97 | class Job extends common_1.Operation {
|
98 | getQueryResultsStream(options) {
|
99 | // placeholder body, overwritten in constructor
|
100 | return new paginator_1.ResourceStream({}, () => { });
|
101 | }
|
102 | constructor(bigQuery, id, options) {
|
103 | let location;
|
104 | const methods = {
|
105 | /**
|
106 | * @callback DeleteJobCallback
|
107 | * @param {?Error} err Request error, if any.
|
108 | * @param {object} apiResponse The full API response.
|
109 | */
|
110 | /**
|
111 | * @typedef {array} DeleteJobResponse
|
112 | * @property {object} 0 The full API response.
|
113 | */
|
114 | /**
|
115 | * Delete the job.
|
116 | *
|
117 | * @see [Jobs: delete API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/delete}
|
118 | *
|
119 | * @method Job#delete
|
120 | * @param {DeleteJobCallback} [callback] The callback function.
|
121 | * @param {?error} callback.err An error returned while making this
|
122 | * request.
|
123 | * @param {object} callback.apiResponse The full API response.
|
124 | * @returns {Promise<DeleteJobResponse>}
|
125 | *
|
126 | * @example
|
127 | * const {BigQuery} = require('@google-cloud/bigquery');
|
128 | * const bigquery = new BigQuery();
|
129 | *
|
130 | * const job = bigquery.job(jobId);
|
131 | * job.delete((err, apiResponse) => {
|
132 | * if (!err) {
|
133 | * // The job was deleted successfully.
|
134 | * }
|
135 | * });
|
136 | *
|
137 | * @example If the callback is omitted a Promise will be returned
|
138 | * const [apiResponse] = await job.delete();
|
139 | */
|
140 | delete: {
|
141 | reqOpts: {
|
142 | method: 'DELETE',
|
143 | uri: '/delete',
|
144 | qs: {
|
145 | get location() {
|
146 | return location;
|
147 | },
|
148 | },
|
149 | },
|
150 | },
|
151 | /**
|
152 | * @callback JobExistsCallback
|
153 | * @param {?Error} err Request error, if any.
|
154 | * @param {boolean} exists Indicates if the job exists.
|
155 | */
|
156 | /**
|
157 | * @typedef {array} JobExistsResponse
|
158 | * @property {boolean} 0 Indicates if the job exists.
|
159 | */
|
160 | /**
|
161 | * Check if the job exists.
|
162 | *
|
163 | * @method Job#exists
|
164 | * @param {JobExistsCallback} [callback] The callback function.
|
165 | * @param {?error} callback.err An error returned while making this
|
166 | * request.
|
167 | * @param {boolean} callback.exists Whether the job exists or not.
|
168 | * @returns {Promise<JobExistsResponse>}
|
169 | *
|
170 | * @example
|
171 | * ```
|
172 | * const {BigQuery} = require('@google-cloud/bigquery');
|
173 | * const bigquery = new BigQuery();
|
174 | *
|
175 | * const job = bigquery.job('job-id');
|
176 | *
|
177 | * job.exists((err, exists) => {});
|
178 | *
|
179 | * //-
|
180 | * // If the callback is omitted, we'll return a Promise.
|
181 | * //-
|
182 | * job.exists().then((data) => {
|
183 | * const exists = data[0];
|
184 | * });
|
185 | * ```
|
186 | */
|
187 | exists: true,
|
188 | /**
|
189 | * @callback GetJobCallback
|
190 | * @param {?Error} err Request error, if any.
|
191 | * @param {Model} model The job.
|
192 | * @param {object} apiResponse The full API response body.
|
193 | */
|
194 | /**
|
195 | * @typedef {array} GetJobResponse
|
196 | * @property {Model} 0 The job.
|
197 | * @property {object} 1 The full API response body.
|
198 | */
|
199 | /**
|
200 | * Get a job if it exists.
|
201 | *
|
202 | * @method Job#get
|
203 | * @param {object} [options] Configuration object.
|
204 | * @param {string} [options.location] The geographic location of the job.
|
205 | * Required except for US and EU.
|
206 | * @param {GetJobCallback} [callback] The callback function.
|
207 | * @param {?error} callback.err An error returned while making this
|
208 | * request.
|
209 | * @param {Job} callback.job The job.
|
210 | * @returns {Promise<GetJobResponse>}
|
211 | *
|
212 | * @example
|
213 | * ```
|
214 | * const {BigQuery} = require('@google-cloud/bigquery');
|
215 | * const bigquery = new BigQuery();
|
216 | *
|
217 | * const job = bigquery.job('job-id');
|
218 | *
|
219 | * job.get((err, job, apiResponse) => {
|
220 | * if (!err) {
|
221 | * // `job.metadata` has been populated.
|
222 | * }
|
223 | * });
|
224 | *
|
225 | * //-
|
226 | * // If the callback is omitted, we'll return a Promise.
|
227 | * //-
|
228 | * job.get().then((data) => {
|
229 | * const job = data[0];
|
230 | * const apiResponse = data[1];
|
231 | * });
|
232 | * ```
|
233 | */
|
234 | get: true,
|
235 | /**
|
236 | * @callback GetJobMetadataCallback
|
237 | * @param {?Error} err Request error, if any.
|
238 | * @param {object} metadata The job metadata.
|
239 | * @param {object} apiResponse The full API response.
|
240 | */
|
241 | /**
|
242 | * @typedef {array} GetJobMetadataResponse
|
243 | * @property {object} 0 The job metadata.
|
244 | * @property {object} 1 The full API response.
|
245 | */
|
246 | /**
|
247 | * Get the metadata of the job. This will mostly be useful for checking
|
248 | * the status of a previously-run job.
|
249 | *
|
250 | * See {@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/get| Jobs: get API Documentation}
|
251 | *
|
252 | * @method Job#getMetadata
|
253 | * @param {GetJobMetadataCallback} [callback] The callback function.
|
254 | * @param {?error} callback.err An error returned while making this
|
255 | * request.
|
256 | * @param {object} callback.metadata The metadata of the job.
|
257 | * @param {object} callback.apiResponse The full API response.
|
258 | * @returns {Promise<GetJobMetadataResponse>}
|
259 | *
|
260 | * @example
|
261 | * ```
|
262 | * const {BigQuery} = require('@google-cloud/bigquery');
|
263 | * const bigquery = new BigQuery();
|
264 | *
|
265 | * const job = bigquery.job('id');
|
266 | * job.getMetadata((err, metadata, apiResponse) => {});
|
267 | *
|
268 | * //-
|
269 | * // If the callback is omitted, we'll return a Promise.
|
270 | * //-
|
271 | * job.getMetadata().then((data) => {
|
272 | * const metadata = data[0];
|
273 | * const apiResponse = data[1];
|
274 | * });
|
275 | * ```
|
276 | */
|
277 | getMetadata: {
|
278 | reqOpts: {
|
279 | qs: {
|
280 | get location() {
|
281 | return location;
|
282 | },
|
283 | },
|
284 | },
|
285 | },
|
286 | };
|
287 | super({
|
288 | parent: bigQuery,
|
289 | baseUrl: '/jobs',
|
290 | id,
|
291 | methods,
|
292 | });
|
293 | Object.defineProperty(this, 'location', {
|
294 | get() {
|
295 | return location;
|
296 | },
|
297 | set(_location) {
|
298 | location = _location;
|
299 | },
|
300 | });
|
301 | this.bigQuery = bigQuery;
|
302 | if (options && options.location) {
|
303 | this.location = options.location;
|
304 | }
|
305 | if (options === null || options === void 0 ? void 0 : options.projectId) {
|
306 | this.projectId = options.projectId;
|
307 | }
|
308 | /**
|
309 | * Get the results of a job as a readable object stream.
|
310 | *
|
311 | * @param {object} options Configuration object. See
|
312 | * {@link Job#getQueryResults} for a complete list of options.
|
313 | * @return {stream}
|
314 | *
|
315 | * @example
|
316 | * ```
|
317 | * const through2 = require('through2');
|
318 | * const fs = require('fs');
|
319 | * const {BigQuery} = require('@google-cloud/bigquery');
|
320 | * const bigquery = new BigQuery();
|
321 | *
|
322 | * const job = bigquery.job('job-id');
|
323 | *
|
324 | * job.getQueryResultsStream()
|
325 | * .pipe(through2.obj(function (row, enc, next) {
|
326 | * this.push(JSON.stringify(row) + '\n');
|
327 | * next();
|
328 | * }))
|
329 | * .pipe(fs.createWriteStream('./test/testdata/testfile.json'));
|
330 | * ```
|
331 | */
|
332 | this.getQueryResultsStream = paginator_1.paginator.streamify('getQueryResultsAsStream_');
|
333 | }
|
334 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
335 | trace_(msg, ...otherArgs) {
|
336 | (0, logger_1.logger)(`[job][${this.id}]`, msg, ...otherArgs);
|
337 | }
|
338 | cancel(callback) {
|
339 | let qs;
|
340 | if (this.location) {
|
341 | qs = { location: this.location };
|
342 | }
|
343 | this.request({
|
344 | method: 'POST',
|
345 | uri: '/cancel',
|
346 | qs,
|
347 | }, callback);
|
348 | }
|
349 | getQueryResults(optionsOrCallback, cb) {
|
350 | const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
|
351 | const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
|
352 | const qs = extend({
|
353 | location: this.location,
|
354 | 'formatOptions.useInt64Timestamp': true,
|
355 | }, options);
|
356 | this.trace_('[getQueryResults]', this.id, options.pageToken, options.startIndex);
|
357 | const wrapIntegers = qs.wrapIntegers ? qs.wrapIntegers : false;
|
358 | delete qs.wrapIntegers;
|
359 | const parseJSON = qs.parseJSON ? qs.parseJSON : false;
|
360 | delete qs.parseJSON;
|
361 | delete qs.job;
|
362 | const timeoutOverride = typeof qs.timeoutMs === 'number' ? qs.timeoutMs : false;
|
363 | const cachedRows = options._cachedRows;
|
364 | const cachedResponse = options._cachedResponse;
|
365 | delete options._cachedRows;
|
366 | delete options._cachedResponse;
|
367 | if (cachedRows) {
|
368 | let nextQuery = null;
|
369 | if (options.pageToken) {
|
370 | nextQuery = Object.assign({}, options, {
|
371 | pageToken: options.pageToken,
|
372 | });
|
373 | }
|
374 | cachedResponse === null || cachedResponse === void 0 ? true : delete cachedResponse.rows;
|
375 | callback(null, cachedRows, nextQuery, cachedResponse);
|
376 | return;
|
377 | }
|
378 | this.bigQuery.request({
|
379 | uri: '/queries/' + this.id,
|
380 | qs,
|
381 | }, (err, resp) => {
|
382 | if (err) {
|
383 | callback(err, null, null, resp);
|
384 | return;
|
385 | }
|
386 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
387 | let rows = [];
|
388 | if (resp.schema && resp.rows) {
|
389 | rows = bigquery_1.BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
|
390 | wrapIntegers,
|
391 | parseJSON,
|
392 | });
|
393 | }
|
394 | let nextQuery = null;
|
395 | if (resp.jobComplete === false) {
|
396 | // Query is still running.
|
397 | nextQuery = Object.assign({}, options);
|
398 | // If timeout override was provided, return error.
|
399 | if (timeoutOverride) {
|
400 | const err = new Error(`The query did not complete before ${timeoutOverride}ms`);
|
401 | callback(err, null, nextQuery, resp);
|
402 | return;
|
403 | }
|
404 | }
|
405 | else if (resp.pageToken) {
|
406 | this.trace_('[getQueryResults] has more pages', resp.pageToken);
|
407 | // More results exist.
|
408 | nextQuery = Object.assign({}, options, {
|
409 | pageToken: resp.pageToken,
|
410 | });
|
411 | delete nextQuery.startIndex;
|
412 | }
|
413 | delete resp.rows;
|
414 | callback(null, rows, nextQuery, resp);
|
415 | });
|
416 | }
|
417 | /**
|
418 | * This method will be called by `getQueryResultsStream()`. It is required to
|
419 | * properly set the `autoPaginate` option value.
|
420 | *
|
421 | * @private
|
422 | */
|
423 | getQueryResultsAsStream_(options, callback) {
|
424 | options = extend({ autoPaginate: false }, options);
|
425 | this.getQueryResults(options, callback);
|
426 | }
|
427 | /**
|
428 | * Poll for a status update. Execute the callback:
|
429 | *
|
430 | * - callback(err): Job failed
|
431 | * - callback(): Job incomplete
|
432 | * - callback(null, metadata): Job complete
|
433 | *
|
434 | * @private
|
435 | *
|
436 | * @param {function} callback
|
437 | */
|
438 | poll_(callback) {
|
439 | this.getMetadata((err, metadata) => {
|
440 | if (!err && metadata.status && metadata.status.errorResult) {
|
441 | err = new common_1.util.ApiError(metadata.status);
|
442 | }
|
443 | if (err) {
|
444 | callback(err);
|
445 | return;
|
446 | }
|
447 | if (metadata.status.state !== 'DONE') {
|
448 | callback(null);
|
449 | return;
|
450 | }
|
451 | callback(null, metadata);
|
452 | });
|
453 | }
|
454 | }
|
455 | exports.Job = Job;
|
456 | /*! Developer Documentation
|
457 | *
|
458 | * These methods can be auto-paginated.
|
459 | */
|
460 | paginator_1.paginator.extend(Job, ['getQueryResults']);
|
461 | /*! Developer Documentation
|
462 | *
|
463 | * All async methods (except for streams) will return a Promise in the event
|
464 | * that a callback is omitted.
|
465 | */
|
466 | (0, promisify_1.promisifyAll)(Job);
|
467 | //# sourceMappingURL=job.js.map |
\ | No newline at end of file |