@google-cloud/bigquery
Version:
Google BigQuery Client Library for Node.js
462 lines • 16.6 kB
JavaScript
/*!
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Job = void 0;
/*!
* @module bigquery/job
*/
const common_1 = require("@google-cloud/common");
const paginator_1 = require("@google-cloud/paginator");
const promisify_1 = require("@google-cloud/promisify");
const extend = require("extend");
const bigquery_1 = require("./bigquery");
const logger_1 = require("./logger");
/**
* @callback QueryResultsCallback
* @param {?Error} err An error returned while making this request.
* @param {array} rows The results of the job.
*/
/**
* @callback ManualQueryResultsCallback
* @param {?Error} err An error returned while making this request.
* @param {array} rows The results of the job.
* @param {?object} nextQuery A pre-made configuration object for your next
* request. This will be `null` if no additional results are available.
* If the query is not yet complete, you may get empty `rows` and
* non-`null` `nextQuery` that you should use for your next request.
* @param {object} apiResponse The full API response.
*/
/**
* Job objects are returned from various places in the BigQuery API:
*
* - {@link BigQuery#getJobs}
* - {@link BigQuery#job}
* - {@link BigQuery#query}
* - {@link BigQuery#createJob}
* - {@link Table#copy}
* - {@link Table#createWriteStream}
* - {@link Table#extract}
* - {@link Table#load}
*
* They can be used to check the status of a running job or fetching the results
* of a previously-executed one.
*
* @class
* @param {BigQuery} bigQuery {@link BigQuery} instance.
* @param {string} id The ID of the job.
* @param {object} [options] Configuration object.
* @param {string} [options.location] The geographic location of the job.
* Required except for US and EU.
*
* @example
* ```
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const job = bigquery.job('job-id');
*
* //-
* // All jobs are event emitters. The status of each job is polled
* // continuously, starting only after you register a "complete" listener.
* //-
* job.on('complete', (metadata) => {
* // The job is complete.
* });
*
* //-
* // Be sure to register an error handler as well to catch any issues which
* // impeded the job.
* //-
* job.on('error', (err) => {
* // An error occurred during the job.
* });
*
* //-
* // To force the Job object to stop polling for updates, simply remove any
* // "complete" listeners you've registered.
* //
* // The easiest way to do this is with `removeAllListeners()`.
* //-
* job.removeAllListeners();
* ```
*/
class Job extends common_1.Operation {
getQueryResultsStream(options) {
// placeholder body, overwritten in constructor
return new paginator_1.ResourceStream({}, () => { });
}
constructor(bigQuery, id, options) {
let location;
const methods = {
/**
* @callback DeleteJobCallback
* @param {?Error} err Request error, if any.
* @param {object} apiResponse The full API response.
*/
/**
* @typedef {array} DeleteJobResponse
* @property {object} 0 The full API response.
*/
/**
* Delete the job.
*
* @see [Jobs: delete API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/delete}
*
* @method Job#delete
* @param {DeleteJobCallback} [callback] The callback function.
* @param {?error} callback.err An error returned while making this
* request.
* @param {object} callback.apiResponse The full API response.
* @returns {Promise<DeleteJobResponse>}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const job = bigquery.job(jobId);
* job.delete((err, apiResponse) => {
* if (!err) {
* // The job was deleted successfully.
* }
* });
*
* @example If the callback is omitted a Promise will be returned
* const [apiResponse] = await job.delete();
*/
delete: {
reqOpts: {
method: 'DELETE',
uri: '/delete',
qs: {
get location() {
return location;
},
},
},
},
/**
* @callback JobExistsCallback
* @param {?Error} err Request error, if any.
* @param {boolean} exists Indicates if the job exists.
*/
/**
* @typedef {array} JobExistsResponse
* @property {boolean} 0 Indicates if the job exists.
*/
/**
* Check if the job exists.
*
* @method Job#exists
* @param {JobExistsCallback} [callback] The callback function.
* @param {?error} callback.err An error returned while making this
* request.
* @param {boolean} callback.exists Whether the job exists or not.
* @returns {Promise<JobExistsResponse>}
*
* @example
* ```
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const job = bigquery.job('job-id');
*
* job.exists((err, exists) => {});
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* job.exists().then((data) => {
* const exists = data[0];
* });
* ```
*/
exists: true,
/**
* @callback GetJobCallback
* @param {?Error} err Request error, if any.
* @param {Model} model The job.
* @param {object} apiResponse The full API response body.
*/
/**
* @typedef {array} GetJobResponse
* @property {Model} 0 The job.
* @property {object} 1 The full API response body.
*/
/**
* Get a job if it exists.
*
* @method Job#get
* @param {object} [options] Configuration object.
* @param {string} [options.location] The geographic location of the job.
* Required except for US and EU.
* @param {GetJobCallback} [callback] The callback function.
* @param {?error} callback.err An error returned while making this
* request.
* @param {Job} callback.job The job.
* @returns {Promise<GetJobResponse>}
*
* @example
* ```
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const job = bigquery.job('job-id');
*
* job.get((err, job, apiResponse) => {
* if (!err) {
* // `job.metadata` has been populated.
* }
* });
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* job.get().then((data) => {
* const job = data[0];
* const apiResponse = data[1];
* });
* ```
*/
get: true,
/**
* @callback GetJobMetadataCallback
* @param {?Error} err Request error, if any.
* @param {object} metadata The job metadata.
* @param {object} apiResponse The full API response.
*/
/**
* @typedef {array} GetJobMetadataResponse
* @property {object} 0 The job metadata.
* @property {object} 1 The full API response.
*/
/**
* Get the metadata of the job. This will mostly be useful for checking
* the status of a previously-run job.
*
* See {@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/get| Jobs: get API Documentation}
*
* @method Job#getMetadata
* @param {GetJobMetadataCallback} [callback] The callback function.
* @param {?error} callback.err An error returned while making this
* request.
* @param {object} callback.metadata The metadata of the job.
* @param {object} callback.apiResponse The full API response.
* @returns {Promise<GetJobMetadataResponse>}
*
* @example
* ```
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const job = bigquery.job('id');
* job.getMetadata((err, metadata, apiResponse) => {});
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* job.getMetadata().then((data) => {
* const metadata = data[0];
* const apiResponse = data[1];
* });
* ```
*/
getMetadata: {
reqOpts: {
qs: {
get location() {
return location;
},
},
},
},
};
super({
parent: bigQuery,
baseUrl: '/jobs',
id,
methods,
});
Object.defineProperty(this, 'location', {
get() {
return location;
},
set(_location) {
location = _location;
},
});
this.bigQuery = bigQuery;
if (options && options.location) {
this.location = options.location;
}
if (options === null || options === void 0 ? void 0 : options.projectId) {
this.projectId = options.projectId;
}
/**
* Get the results of a job as a readable object stream.
*
* @param {object} options Configuration object. See
* {@link Job#getQueryResults} for a complete list of options.
* @return {stream}
*
* @example
* ```
* const through2 = require('through2');
* const fs = require('fs');
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const job = bigquery.job('job-id');
*
* job.getQueryResultsStream()
* .pipe(through2.obj(function (row, enc, next) {
* this.push(JSON.stringify(row) + '\n');
* next();
* }))
* .pipe(fs.createWriteStream('./test/testdata/testfile.json'));
* ```
*/
this.getQueryResultsStream = paginator_1.paginator.streamify('getQueryResultsAsStream_');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
trace_(msg, ...otherArgs) {
(0, logger_1.logger)(`[job][${this.id}]`, msg, ...otherArgs);
}
cancel(callback) {
let qs;
if (this.location) {
qs = { location: this.location };
}
this.request({
method: 'POST',
uri: '/cancel',
qs,
}, callback);
}
getQueryResults(optionsOrCallback, cb) {
const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const qs = extend({
location: this.location,
'formatOptions.useInt64Timestamp': true,
}, options);
this.trace_('[getQueryResults]', this.id, options.pageToken, options.startIndex);
const wrapIntegers = qs.wrapIntegers ? qs.wrapIntegers : false;
delete qs.wrapIntegers;
const parseJSON = qs.parseJSON ? qs.parseJSON : false;
delete qs.parseJSON;
delete qs.job;
const timeoutOverride = typeof qs.timeoutMs === 'number' ? qs.timeoutMs : false;
if (options._cachedRows) {
let nextQuery = null;
if (options.pageToken) {
nextQuery = Object.assign({}, options, {
pageToken: options.pageToken,
});
delete nextQuery._cachedRows;
}
callback(null, options._cachedRows, nextQuery);
return;
}
this.bigQuery.request({
uri: '/queries/' + this.id,
qs,
}, (err, resp) => {
if (err) {
callback(err, null, null, resp);
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let rows = [];
if (resp.schema && resp.rows) {
rows = bigquery_1.BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
wrapIntegers,
parseJSON,
});
}
let nextQuery = null;
if (resp.jobComplete === false) {
// Query is still running.
nextQuery = Object.assign({}, options);
// If timeout override was provided, return error.
if (timeoutOverride) {
const err = new Error(`The query did not complete before ${timeoutOverride}ms`);
callback(err, null, nextQuery, resp);
return;
}
}
else if (resp.pageToken) {
this.trace_('[getQueryResults] has more pages', resp.pageToken);
// More results exist.
nextQuery = Object.assign({}, options, {
pageToken: resp.pageToken,
});
delete nextQuery.startIndex;
}
callback(null, rows, nextQuery, resp);
});
}
/**
* This method will be called by `getQueryResultsStream()`. It is required to
* properly set the `autoPaginate` option value.
*
* @private
*/
getQueryResultsAsStream_(options, callback) {
options = extend({ autoPaginate: false }, options);
this.getQueryResults(options, callback);
}
/**
* Poll for a status update. Execute the callback:
*
* - callback(err): Job failed
* - callback(): Job incomplete
* - callback(null, metadata): Job complete
*
* @private
*
* @param {function} callback
*/
poll_(callback) {
this.getMetadata((err, metadata) => {
if (!err && metadata.status && metadata.status.errorResult) {
err = new common_1.util.ApiError(metadata.status);
}
if (err) {
callback(err);
return;
}
if (metadata.status.state !== 'DONE') {
callback(null);
return;
}
callback(null, metadata);
});
}
}
exports.Job = Job;
/*! Developer Documentation
*
* These methods can be auto-paginated.
*/
paginator_1.paginator.extend(Job, ['getQueryResults']);
/*! Developer Documentation
*
* All async methods (except for streams) will return a Promise in the event
* that a callback is omitted.
*/
(0, promisify_1.promisifyAll)(Job);
//# sourceMappingURL=job.js.map
;