UNPKG

16.8 kBJavaScriptView Raw
1"use strict";
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 */
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.Job = void 0;
19/*!
20 * @module bigquery/job
21 */
22const common_1 = require("@google-cloud/common");
23const paginator_1 = require("@google-cloud/paginator");
24const promisify_1 = require("@google-cloud/promisify");
25const extend = require("extend");
26const bigquery_1 = require("./bigquery");
27const 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 */
97class 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}
455exports.Job = Job;
456/*! Developer Documentation
457 *
458 * These methods can be auto-paginated.
459 */
460paginator_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