"use strict";
/*!
* Copyright 2019 Google LLC
*
* 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 });
const common = require("@google-cloud/common");
const paginator_1 = require("@google-cloud/paginator");
const promisify_1 = require("@google-cloud/promisify");
const arrify = require("arrify");
const big_js_1 = require("big.js");
const extend = require("extend");
const format = require('string-format-obj');
const is = require("is");
const uuid = require("uuid");
const dataset_1 = require("./dataset");
const job_1 = require("./job");
const table_1 = require("./table");
/**
* @typedef {object} BigQueryOptions
* @property {string} [projectId] The project ID from the Google Developer's
* Console, e.g. 'grape-spaceship-123'. We will also check the environment
* variable `GCLOUD_PROJECT` for your project ID. If your app is running in
* an environment which supports {@link
* https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
* Application Default Credentials}, your project ID will be detected
* automatically.
* @property {string} [keyFilename] Full path to the a .json, .pem, or .p12 key
* downloaded from the Google Developers Console. If you provide a path to a
* JSON file, the `projectId` option above is not necessary. NOTE: .pem and
* .p12 require you to specify the `email` option as well.
* @property {string} [token] An OAUTH access token. If provided, we will not
* manage fetching, re-using, and re-minting access tokens.
* @property {string} [email] Account email address. Required when using a .pem
* or .p12 keyFilename.
* @property {object} [credentials] Credentials object.
* @property {string} [credentials.client_email]
* @property {string} [credentials.private_key]
* @property {boolean} [autoRetry=true] Automatically retry requests if the
* response is related to rate limits or certain intermittent server errors.
* We will exponentially backoff subsequent requests by default.
* @property {number} [maxRetries=3] Maximum number of automatic retries
* attempted before returning the error.
* @property {Constructor} [promise] Custom promise module to use instead of
* native Promises.
* @property {string} [location] The geographic location of all datasets and
* jobs referenced and created through the client.
* @property {string[]} [scopes] Additional OAuth scopes to use in requests. For
* example, to access an external data source, you may need the
* `https://www.googleapis.com/auth/drive.readonly` scope.
* @property {string=} apiEndpoint The API endpoint of the service used to make requests. Defaults to `bigquery.googleapis.com`.
*/
/**
* In the following examples from this page and the other modules (`Dataset`,
* `Table`, etc.), we are going to be using a dataset from
* [data.gov](http://goo.gl/f2SXcb) of higher education institutions.
*
* We will create a table with the correct schema, import the public CSV file
* into that table, and query it for data.
*
* @class
*
* @see [What is BigQuery?]{@link https://cloud.google.com/bigquery/what-is-bigquery}
*
* @param {BigQueryOptions} options Constructor options.
*
* @example <caption>Install the client library with <a href="https://www.npmjs.com/">npm</a>:</caption>
* npm install @google-cloud/bigquery
*
* @example <caption>Import the client library</caption>
* const {BigQuery} = require('@google-cloud/bigquery');
*
* @example <caption>Create a client that uses <a href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application Default Credentials (ADC)</a>:</caption>
* const bigquery = new BigQuery();
*
* @example <caption>Create a client with <a href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit credentials</a>:</caption>
* const bigquery = new BigQuery({
* projectId: 'your-project-id',
* keyFilename: '/path/to/keyfile.json'
* });
*
* @example <caption>include:samples/quickstart.js</caption>
* region_tag:bigquery_quickstart
* Full quickstart example:
*/
class BigQuery extends common.Service {
constructor(options = {}) {
options = Object.assign({}, options, {
apiEndpoint: options.apiEndpoint || 'bigquery.googleapis.com',
});
const config = {
apiEndpoint: options.apiEndpoint,
baseUrl: `https://${options.apiEndpoint}/bigquery/v2`,
scopes: ['https://www.googleapis.com/auth/bigquery'],
packageJson: require('../../package.json'),
};
if (options.scopes) {
config.scopes = config.scopes.concat(options.scopes);
}
super(config, options);
/**
* @name BigQuery#location
* @type {string}
*/
this.location = options.location;
/**
* Run a query scoped to your project as a readable object stream.
*
* @param {object} query Configuration object. See {@link Query} for a complete
* list of options.
* @returns {stream}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const query = 'SELECT url FROM `publicdata.samples.github_nested` LIMIT
* 100';
*
* bigquery.createQueryStream(query)
* .on('error', console.error)
* .on('data', function(row) {
* // row is a result from your query.
* })
* .on('end', function() {
* // All rows retrieved.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* bigquery.createQueryStream(query)
* .on('data', function(row) {
* this.end();
* });
*/
this.createQueryStream = paginator_1.paginator.streamify('queryAsStream_');
/**
* List all or some of the {@link Dataset} objects in your project as
* a readable object stream.
*
* @param {object} [options] Configuration object. See
* {@link BigQuery#getDatasets} for a complete list of options.
* @returns {stream}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* bigquery.getDatasetsStream()
* .on('error', console.error)
* .on('data', function(dataset) {
* // dataset is a Dataset object.
* })
* .on('end', function() {
* // All datasets retrieved.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* bigquery.getDatasetsStream()
* .on('data', function(dataset) {
* this.end();
* });
*/
this.getDatasetsStream = paginator_1.paginator.streamify('getDatasets');
/**
* List all or some of the {@link Job} objects in your project as a
* readable object stream.
*
* @param {object} [options] Configuration object. See
* {@link BigQuery#getJobs} for a complete list of options.
* @returns {stream}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* bigquery.getJobsStream()
* .on('error', console.error)
* .on('data', function(job) {
* // job is a Job object.
* })
* .on('end', function() {
* // All jobs retrieved.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* bigquery.getJobsStream()
* .on('data', function(job) {
* this.end();
* });
*/
this.getJobsStream = paginator_1.paginator.streamify('getJobs');
}
/**
* Merge a rowset returned from the API with a table schema.
*
* @private
*
* @param {object} schema
* @param {array} rows
* @returns {array} Fields using their matching names from the table's schema.
*/
static mergeSchemaWithRows_(schema, rows) {
return arrify(rows)
.map(mergeSchema)
.map(flattenRows);
function mergeSchema(row) {
return row.f.map((field, index) => {
const schemaField = schema.fields[index];
let value = field.v;
if (schemaField.mode === 'REPEATED') {
value = value.map(val => {
return convert(schemaField, val.v);
});
}
else {
value = convert(schemaField, value);
}
// tslint:disable-next-line no-any
const fieldObject = {};
fieldObject[schemaField.name] = value;
return fieldObject;
});
}
// tslint:disable-next-line no-any
function convert(schemaField, value) {
if (is.null(value)) {
return value;
}
switch (schemaField.type) {
case 'BOOLEAN':
case 'BOOL': {
value = value.toLowerCase() === 'true';
break;
}
case 'BYTES': {
value = Buffer.from(value, 'base64');
break;
}
case 'FLOAT':
case 'FLOAT64': {
value = Number(value);
break;
}
case 'INTEGER':
case 'INT64': {
value = Number(value);
break;
}
case 'NUMERIC': {
value = new big_js_1.Big(value);
break;
}
case 'RECORD': {
value = BigQuery.mergeSchemaWithRows_(schemaField, value).pop();
break;
}
case 'DATE': {
value = BigQuery.date(value);
break;
}
case 'DATETIME': {
value = BigQuery.datetime(value);
break;
}
case 'TIME': {
value = BigQuery.time(value);
break;
}
case 'TIMESTAMP': {
value = BigQuery.timestamp(new Date(value * 1000));
break;
}
case 'GEOGRAPHY': {
value = BigQuery.geography(value);
break;
}
default:
break;
}
return value;
}
// tslint:disable-next-line no-any
function flattenRows(rows) {
return rows.reduce((acc, row) => {
const key = Object.keys(row)[0];
acc[key] = row[key];
return acc;
}, {});
}
}
/**
* The `DATE` type represents a logical calendar date, independent of time
* zone. It does not represent a specific 24-hour time period. Rather, a given
* DATE value represents a different 24-hour period when interpreted in
* different time zones, and may represent a shorter or longer day during
* Daylight Savings Time transitions.
*
* @param {object|string} value The date. If a string, this should be in the
* format the API describes: `YYYY-[M]M-[D]D`.
* Otherwise, provide an object.
* @param {string|number} value.year Four digits.
* @param {string|number} value.month One or two digits.
* @param {string|number} value.day One or two digits.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
* const date = bigquery.date('2017-01-01');
*
* //-
* // Alternatively, provide an object.
* //-
* const date2 = bigquery.date({
* year: 2017,
* month: 1,
* day: 1
* });
*/
static date(value) {
return new BigQueryDate(value);
}
/**
* @param {object|string} value The date. If a string, this should be in the
* format the API describes: `YYYY-[M]M-[D]D`.
* Otherwise, provide an object.
* @param {string|number} value.year Four digits.
* @param {string|number} value.month One or two digits.
* @param {string|number} value.day One or two digits.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const date = BigQuery.date('2017-01-01');
*
* //-
* // Alternatively, provide an object.
* //-
* const date2 = BigQuery.date({
* year: 2017,
* month: 1,
* day: 1
* });
*/
date(value) {
return BigQuery.date(value);
}
/**
* A `DATETIME` data type represents a point in time. Unlike a `TIMESTAMP`,
* this does not refer to an absolute instance in time. Instead, it is the
* civil time, or the time that a user would see on a watch or calendar.
*
* @method BigQuery.datetime
* @param {object|string} value The time. If a string, this should be in the
* format the API describes: `YYYY-[M]M-[D]D[ [H]H:[M]M:[S]S[.DDDDDD]]`.
* Otherwise, provide an object.
* @param {string|number} value.year Four digits.
* @param {string|number} value.month One or two digits.
* @param {string|number} value.day One or two digits.
* @param {string|number} [value.hours] One or two digits (`00` - `23`).
* @param {string|number} [value.minutes] One or two digits (`00` - `59`).
* @param {string|number} [value.seconds] One or two digits (`00` - `59`).
* @param {string|number} [value.fractional] Up to six digits for microsecond
* precision.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const datetime = BigQuery.datetime('2017-01-01 13:00:00');
*
* //-
* // Alternatively, provide an object.
* //-
* const datetime = BigQuery.datetime({
* year: 2017,
* month: 1,
* day: 1,
* hours: 14,
* minutes: 0,
* seconds: 0
* });
*/
/**
* A `DATETIME` data type represents a point in time. Unlike a `TIMESTAMP`,
* this does not refer to an absolute instance in time. Instead, it is the
* civil time, or the time that a user would see on a watch or calendar.
*
* @method BigQuery#datetime
* @param {object|string} value The time. If a string, this should be in the
* format the API describes: `YYYY-[M]M-[D]D[ [H]H:[M]M:[S]S[.DDDDDD]]`.
* Otherwise, provide an object.
* @param {string|number} value.year Four digits.
* @param {string|number} value.month One or two digits.
* @param {string|number} value.day One or two digits.
* @param {string|number} [value.hours] One or two digits (`00` - `23`).
* @param {string|number} [value.minutes] One or two digits (`00` - `59`).
* @param {string|number} [value.seconds] One or two digits (`00` - `59`).
* @param {string|number} [value.fractional] Up to six digits for microsecond
* precision.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
* const datetime = bigquery.datetime('2017-01-01 13:00:00');
*
* //-
* // Alternatively, provide an object.
* //-
* const datetime = bigquery.datetime({
* year: 2017,
* month: 1,
* day: 1,
* hours: 14,
* minutes: 0,
* seconds: 0
* });
*/
static datetime(value) {
return new BigQueryDatetime(value);
}
datetime(value) {
return BigQuery.datetime(value);
}
/**
* A `TIME` data type represents a time, independent of a specific date.
*
* @method BigQuery.time
* @param {object|string} value The time. If a string, this should be in the
* format the API describes: `[H]H:[M]M:[S]S[.DDDDDD]`. Otherwise, provide
* an object.
* @param {string|number} [value.hours] One or two digits (`00` - `23`).
* @param {string|number} [value.minutes] One or two digits (`00` - `59`).
* @param {string|number} [value.seconds] One or two digits (`00` - `59`).
* @param {string|number} [value.fractional] Up to six digits for microsecond
* precision.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const time = BigQuery.time('14:00:00'); // 2:00 PM
*
* //-
* // Alternatively, provide an object.
* //-
* const time = BigQuery.time({
* hours: 14,
* minutes: 0,
* seconds: 0
* });
*/
/**
* A `TIME` data type represents a time, independent of a specific date.
*
* @method BigQuery#time
* @param {object|string} value The time. If a string, this should be in the
* format the API describes: `[H]H:[M]M:[S]S[.DDDDDD]`. Otherwise, provide
* an object.
* @param {string|number} [value.hours] One or two digits (`00` - `23`).
* @param {string|number} [value.minutes] One or two digits (`00` - `59`).
* @param {string|number} [value.seconds] One or two digits (`00` - `59`).
* @param {string|number} [value.fractional] Up to six digits for microsecond
* precision.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
* const time = bigquery.time('14:00:00'); // 2:00 PM
*
* //-
* // Alternatively, provide an object.
* //-
* const time = bigquery.time({
* hours: 14,
* minutes: 0,
* seconds: 0
* });
*/
static time(value) {
return new BigQueryTime(value);
}
time(value) {
return BigQuery.time(value);
}
/**
* A timestamp represents an absolute point in time, independent of any time
* zone or convention such as Daylight Savings Time.
*
* @method BigQuery.timestamp
* @param {Date|string} value The time.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const timestamp = BigQuery.timestamp(new Date());
*/
/**
* A timestamp represents an absolute point in time, independent of any time
* zone or convention such as Daylight Savings Time.
*
* @method BigQuery#timestamp
* @param {Date|string} value The time.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
* const timestamp = bigquery.timestamp(new Date());
*/
static timestamp(value) {
return new BigQueryTimestamp(value);
}
timestamp(value) {
return BigQuery.timestamp(value);
}
/**
* A geography value represents a surface area on the Earth
* in Well-known Text (WKT) format.
*
* @method BigQuery.geography
* @param {string} value The geospatial data.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const geography = BigQuery.geography('POINT(1, 2)');
*/
/**
* A geography value represents a surface area on the Earth
* in Well-known Text (WKT) format.
*
* @method BigQuery#geography
* @param {string} value The geospatial data.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
* const geography = bigquery.geography('POINT(1, 2)');
*/
static geography(value) {
return new Geography(value);
}
geography(value) {
return BigQuery.geography(value);
}
/**
* Return a value's provided type.
*
* @private
*
* @throws {error} If the type provided is invalid.
*
* @see [Data Type]{@link https://cloud.google.com/bigquery/data-types}
*
* @param {*} providedType The type.
* @returns {string} The valid type provided.
*/
static getTypeDescriptorFromProvidedType_(providedType) {
// The list of types can be found in src/types.d.ts
const VALID_TYPES = [
'DATE',
'DATETIME',
'TIME',
'TIMESTAMP',
'BYTES',
'NUMERIC',
'BOOL',
'INT64',
'FLOAT64',
'STRING',
'GEOGRAPHY',
'ARRAY',
'STRUCT',
];
if (is.array(providedType)) {
providedType = providedType;
return {
type: 'ARRAY',
arrayType: BigQuery.getTypeDescriptorFromProvidedType_(providedType[0]),
};
}
else if (is.object(providedType)) {
return {
type: 'STRUCT',
structTypes: Object.keys(providedType).map(prop => {
return {
name: prop,
type: BigQuery.getTypeDescriptorFromProvidedType_(providedType[prop]),
};
}),
};
}
providedType = providedType.toUpperCase();
if (!VALID_TYPES.includes(providedType)) {
throw new Error(`Invalid type provided: "${providedType}"`);
}
return { type: providedType.toUpperCase() };
}
/**
* Detect a value's type.
*
* @private
*
* @throws {error} If the type could not be detected.
*
* @see [Data Type]{@link https://cloud.google.com/bigquery/data-types}
*
* @param {*} value The value.
* @returns {string} The type detected from the value.
*/
static getTypeDescriptorFromValue_(
// tslint:disable-next-line: no-any
value) {
let typeName;
if (value === null) {
throw new Error('Type must be provided for null values.');
}
if (value instanceof BigQueryDate) {
typeName = 'DATE';
}
else if (value instanceof BigQueryDatetime) {
typeName = 'DATETIME';
}
else if (value instanceof BigQueryTime) {
typeName = 'TIME';
}
else if (value instanceof BigQueryTimestamp) {
typeName = 'TIMESTAMP';
}
else if (value instanceof Buffer) {
typeName = 'BYTES';
}
else if (value instanceof big_js_1.Big) {
typeName = 'NUMERIC';
}
else if (is.array(value)) {
if (value.length === 0) {
throw new Error('Type must be provided for empty array.');
}
return {
type: 'ARRAY',
arrayType: BigQuery.getTypeDescriptorFromValue_(value[0]),
};
}
else if (is.boolean(value)) {
typeName = 'BOOL';
}
else if (is.number(value)) {
typeName = value % 1 === 0 ? 'INT64' : 'FLOAT64';
}
else if (is.object(value)) {
return {
type: 'STRUCT',
structTypes: Object.keys(value).map(prop => {
return {
name: prop,
type: BigQuery.getTypeDescriptorFromValue_(value[prop]),
};
}),
};
}
else if (is.string(value)) {
typeName = 'STRING';
}
if (!typeName) {
throw new Error([
'This value could not be translated to a BigQuery data type.',
value,
].join('\n'));
}
return {
type: typeName,
};
}
/**
* Convert a value into a `queryParameter` object.
*
* @private
*
* @see [Jobs.query API Reference Docs (see `queryParameters`)]{@link https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#request-body}
*
* @param {*} value The value.
* @returns {object} A properly-formed `queryParameter` object.
*/
static valueToQueryParameter_(
// tslint:disable-next-line: no-any
value, providedType) {
if (is.date(value)) {
value = BigQuery.timestamp(value);
}
let parameterType;
if (providedType) {
parameterType = BigQuery.getTypeDescriptorFromProvidedType_(providedType);
}
else {
parameterType = BigQuery.getTypeDescriptorFromValue_(value);
}
const queryParameter = { parameterType, parameterValue: {} };
const typeName = queryParameter.parameterType.type;
if (typeName === 'ARRAY') {
queryParameter.parameterValue.arrayValues = value.map(itemValue => {
const value = getValue(itemValue, parameterType.arrayType);
if (is.object(value) || is.array(value)) {
if (is.array(providedType)) {
providedType = providedType;
return BigQuery.valueToQueryParameter_(value, providedType[0])
.parameterValue;
}
else {
return BigQuery.valueToQueryParameter_(value).parameterValue;
}
}
return { value };
});
}
else if (typeName === 'STRUCT') {
queryParameter.parameterValue.structValues = Object.keys(value).reduce((structValues, prop) => {
let nestedQueryParameter;
if (providedType) {
nestedQueryParameter = BigQuery.valueToQueryParameter_(value[prop], providedType[prop]);
}
else {
nestedQueryParameter = BigQuery.valueToQueryParameter_(value[prop]);
}
// tslint:disable-next-line no-any
structValues[prop] = nestedQueryParameter.parameterValue;
return structValues;
}, {});
}
else {
queryParameter.parameterValue.value = getValue(value, parameterType);
}
return queryParameter;
// tslint:disable-next-line no-any
function getValue(value, type) {
return isCustomType(type) ? value.value : value;
}
function isCustomType({ type }) {
return type.indexOf('TIME') > -1 || type.indexOf('DATE') > -1;
}
}
/**
* Create a dataset.
*
* @see [Datasets: insert API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/insert}
*
* @param {string} id ID of the dataset to create.
* @param {object} [options] See a
* [Dataset
* resource](https://cloud.google.com/bigquery/docs/reference/v2/datasets#resource).
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request
* @param {Dataset} callback.dataset The newly created dataset
* @param {object} callback.apiResponse The full API response.
* @returns {Promise}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* bigquery.createDataset('my-dataset', function(err, dataset, apiResponse)
* {});
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* bigquery.createDataset('my-dataset').then(function(data) {
* const dataset = data[0];
* const apiResponse = data[1];
* });
*/
createDataset(id, optionsOrCallback, cb) {
const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
this.request({
method: 'POST',
uri: '/datasets',
json: extend(true, {
location: this.location,
}, options, {
datasetReference: {
datasetId: id,
},
}),
}, (err, resp) => {
if (err) {
callback(err, null, resp);
return;
}
const dataset = this.dataset(id);
dataset.metadata = resp;
callback(null, dataset, resp);
});
}
/**
* Run a query as a job. No results are immediately returned. Instead, your
* callback will be executed with a {@link Job} object that you must
* ping for the results. See the Job documentation for explanations of how to
* check on the status of the job.
*
* @see [Jobs: insert API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/insert}
*
* @param {object|string} options The configuration object. This must be in
* the format of the [`configuration.query`](http://goo.gl/wRpHvR)
* property of a Jobs resource. If a string is provided, this is used as the
* query string, and all other options are defaulted.
* @param {Table} [options.destination] The table to save the
* query's results to. If omitted, a new table will be created.
* @param {boolean} [options.dryRun] If set, don't actually run this job. A
* valid query will update the job with processing statistics. These can
* be accessed via `job.metadata`.
* @param {string} [options.location] The geographic location of the job.
* Required except for US and EU.
* @param {string} [options.jobId] Custom job id.
* @param {string} [options.jobPrefix] Prefix to apply to the job id.
* @param {string} options.query A query string, following the BigQuery query
* syntax, of the query to execute.
* @param {boolean} [options.useLegacySql=false] Option to use legacy sql syntax.
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request.
* @param {Job} callback.job The newly created job for your query.
* @param {object} callback.apiResponse The full API response.
* @returns {Promise}
*
* @throws {Error} If a query is not specified.
* @throws {Error} If a Table is not provided as a destination.
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const query = 'SELECT url FROM `publicdata.samples.github_nested` LIMIT
* 100';
*
* //-
* // You may pass only a query string, having a new table created to store
* the
* // results of the query.
* //-
* bigquery.createQueryJob(query, function(err, job) {});
*
* //-
* // You can also control the destination table by providing a
* // {@link Table} object.
* //-
* bigquery.createQueryJob({
* destination: bigquery.dataset('higher_education').table('institutions'),
* query: query
* }, function(err, job) {});
*
* //-
* // After you have run `createQueryJob`, your query will execute in a job.
* Your
* // callback is executed with a {@link Job} object so that you may
* // check for the results.
* //-
* bigquery.createQueryJob(query, function(err, job) {
* if (!err) {
* job.getQueryResults(function(err, rows, apiResponse) {});
* }
* });
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* bigquery.createQueryJob(query).then(function(data) {
* const job = data[0];
* const apiResponse = data[1];
*
* return job.getQueryResults();
* });
*/
createQueryJob(opts, callback) {
const options = typeof opts === 'object' ? opts : { query: opts };
if ((!options || !options.query) && !options.pageToken) {
throw new Error('A SQL query string is required.');
}
// tslint:disable-next-line no-any
const query = extend(true, {
useLegacySql: false,
}, options);
if (options.destination) {
if (!(options.destination instanceof table_1.Table)) {
throw new Error('Destination must be a Table object.');
}
query.destinationTable = {
datasetId: options.destination.dataset.id,
projectId: options.destination.dataset.bigQuery.projectId,
tableId: options.destination.id,
};
delete query.destination;
}
if (query.params) {
query.parameterMode = is.array(query.params) ? 'positional' : 'named';
if (query.parameterMode === 'named') {
query.queryParameters = [];
// tslint:disable-next-line forin
for (const namedParameter in query.params) {
const value = query.params[namedParameter];
let queryParameter;
if (query.types) {
if (!is.object(query.types)) {
throw new Error('Provided types must match the value type passed to `params`');
}
if (query.types[namedParameter]) {
queryParameter = BigQuery.valueToQueryParameter_(value, query.types[namedParameter]);
}
else {
throw new Error(`Type not provided for parameter: ${namedParameter}`);
}
}
else {
queryParameter = BigQuery.valueToQueryParameter_(value);
}
queryParameter.name = namedParameter;
query.queryParameters.push(queryParameter);
}
}
else {
query.queryParameters = [];
if (query.types) {
if (!is.array(query.types)) {
throw new Error('Provided types must match the value type passed to `params`');
}
if (query.params.length !== query.types.length) {
throw new Error('Incorrect number of parameter types provided.');
}
query.params.forEach((value, i) => {
const queryParameter = BigQuery.valueToQueryParameter_(value, query.types[i]);
query.queryParameters.push(queryParameter);
});
}
else {
query.params.forEach((value) => {
const queryParameter = BigQuery.valueToQueryParameter_(value);
query.queryParameters.push(queryParameter);
});
}
}
delete query.params;
}
// tslint:disable-next-line no-any
const reqOpts = {
configuration: {
query,
},
};
if (query.dryRun) {
reqOpts.configuration.dryRun = query.dryRun;
delete query.dryRun;
}
if (query.jobPrefix) {
reqOpts.jobPrefix = query.jobPrefix;
delete query.jobPrefix;
}
if (query.location) {
reqOpts.location = query.location;
delete query.location;
}
if (query.jobId) {
reqOpts.jobId = query.jobId;
delete query.jobId;
}
this.createJob(reqOpts, callback);
}
/**
* Creates a job. Typically when creating a job you'll have a very specific
* task in mind. For this we recommend one of the following methods:
*
* - {@link BigQuery#createQueryJob}
* - {@link Table#createCopyJob}
* - {@link Table#createCopyFromJob}
* - {@link Table#createExtractJob}
* - {@link Table#createLoadJob}
*
* However in the event you need a finer level of control over the job
* creation, you can use this method to pass in a raw [Job
* resource](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs)
* object.
*
* @see [Jobs Overview]{@link https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs}
* @see [Jobs: insert API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/insert}
*
* @param {object} options Object in the form of a [Job resource](https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs);
* @param {string} [options.jobId] Custom job id.
* @param {string} [options.jobPrefix] Prefix to apply to the job id.
* @param {string} [options.location] The geographic location of the job.
* Required except for US and EU.
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request.
* @param {Job} callback.job The newly created job.
* @param {object} callback.apiResponse The full API response.
* @returns {Promise}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const options = {
* configuration: {
* query: {
* query: 'SELECT url FROM `publicdata.samples.github_nested` LIMIT 100'
* }
* }
* };
*
* bigquery.createJob(options, function(err, job) {
* if (err) {
* // Error handling omitted.
* }
*
* job.getQueryResults(function(err, rows) {});
* });
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* bigquery.createJob(options).then(function(data) {
* const job = data[0];
*
* return job.getQueryResults();
* });
*/
createJob(options, callback) {
// tslint:disable-next-line no-any
const reqOpts = extend({}, options);
let jobId = reqOpts.jobId || uuid.v4();
if (reqOpts.jobId) {
delete reqOpts.jobId;
}
if (reqOpts.jobPrefix) {
jobId = reqOpts.jobPrefix + jobId;
delete reqOpts.jobPrefix;
}
reqOpts.jobReference = {
projectId: this.projectId,
jobId,
location: this.location,
};
if (options.location) {
reqOpts.jobReference.location = options.location;
delete reqOpts.location;
}
this.request({
method: 'POST',
uri: '/jobs',
json: reqOpts,
}, (err, resp) => {
if (err) {
callback(err, null, resp);
return;
}
if (resp.status.errors) {
err = new common.util.ApiError({
errors: resp.status.errors,
response: resp,
});
}
const job = this.job(jobId, {
location: resp.jobReference.location,
});
job.metadata = resp;
callback(err, job, resp);
});
}
/**
* Create a reference to a dataset.
*
* @param {string} id ID of the dataset.
* @param {object} [options] Dataset options.
* @param {string} [options.location] The geographic location of the dataset.
* Required except for US and EU.
* @returns {Dataset}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
* const dataset = bigquery.dataset('higher_education');
*/
dataset(id, options) {
if (typeof id !== 'string') {
throw new TypeError('A dataset ID is required.');
}
if (this.location) {
options = extend({ location: this.location }, options);
}
return new dataset_1.Dataset(this, id, options);
}
/**
* List all or some of the datasets in your project.
*
* @see [Datasets: list API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/list}
*
* @param {object} [options] Configuration object.
* @param {boolean} [options.all] List all datasets, including hidden ones.
* @param {boolean} [options.autoPaginate] Have pagination handled automatically.
* Default: true.
* @param {number} [options.maxApiCalls] Maximum number of API calls to make.
* @param {number} [options.maxResults] Maximum number of results to return.
* @param {string} [options.pageToken] Token returned from a previous call, to
* request the next page of results.
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request
* @param {Dataset[]} callback.datasets The list of datasets in your project.
* @returns {Promise}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* bigquery.getDatasets(function(err, datasets) {
* if (!err) {
* // datasets is an array of Dataset objects.
* }
* });
*
* //-
* // To control how many API requests are made and page through the results
* // manually, set `autoPaginate` to `false`.
* //-
* function manualPaginationCallback(err, datasets, nextQuery, apiResponse) {
* if (nextQuery) {
* // More results exist.
* bigquery.getDatasets(nextQuery, manualPaginationCallback);
* }
* }
*
* bigquery.getDatasets({
* autoPaginate: false
* }, manualPaginationCallback);
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* bigquery.getDatasets().then(function(datasets) {});
*/
getDatasets(optionsOrCallback, cb) {
const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
this.request({
uri: '/datasets',
qs: options,
}, (err, resp) => {
if (err) {
callback(err, null, null, resp);
return;
}
let nextQuery = null;
if (resp.nextPageToken) {
nextQuery = extend({}, options, {
pageToken: resp.nextPageToken,
});
}
// tslint:disable-next-line no-any
const datasets = (resp.datasets || []).map((dataset) => {
const ds = this.dataset(dataset.datasetReference.datasetId, {
location: dataset.location,
});
ds.metadata = dataset;
return ds;
});
callback(null, datasets, nextQuery, resp);
});
}
/**
* Get all of the jobs from your project.
*
* @see [Jobs: list API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/list}
*
* @param {object} [options] Configuration object.
* @param {boolean} [options.allUsers] Display jobs owned by all users in the
* project.
* @param {boolean} [options.autoPaginate] Have pagination handled
* automatically. Default: true.
* @param {number} [options.maxApiCalls] Maximum number of API calls to make.
* @param {number} [options.maxResults] Maximum number of results to return.
* @param {string} [options.pageToken] Token returned from a previous call, to
* request the next page of results.
* @param {string} [options.projection] Restrict information returned to a set
* of selected fields. Acceptable values are "full", for all job data, and
* "minimal", to not include the job configuration.
* @param {string} [options.stateFilter] Filter for job state. Acceptable
* values are "done", "pending", and "running". Sending an array to this
* option performs a disjunction.
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request
* @param {Job[]} callback.jobs The list of jobs in your
* project.
* @returns {Promise}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* bigquery.getJobs(function(err, jobs) {
* if (!err) {
* // jobs is an array of Job objects.
* }
* });
*
* //-
* // To control how many API requests are made and page through the results
* // manually, set `autoPaginate` to `false`.
* //-
* function manualPaginationCallback(err, jobs, nextQuery, apiRespose) {
* if (nextQuery) {
* // More results exist.
* bigquery.getJobs(nextQuery, manualPaginationCallback);
* }
* }
*
* bigquery.getJobs({
* autoPaginate: false
* }, manualPaginationCallback);
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* bigquery.getJobs().then(function(data) {
* const jobs = data[0];
* });
*/
getJobs(optionsOrCallback, cb) {
const that = this;
const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
this.request({
uri: '/jobs',
qs: options,
useQuerystring: true,
}, (err, resp) => {
if (err) {
callback(err, null, null, resp);
return;
}
let nextQuery = null;
if (resp.nextPageToken) {
nextQuery = extend({}, options, {
pageToken: resp.nextPageToken,
});
}
// tslint:disable-next-line no-any
const jobs = (resp.jobs || []).map((jobObject) => {
const job = that.job(jobObject.jobReference.jobId, {
location: jobObject.jobReference.location,
});
job.metadata = jobObject;
return job;
});
callback(null, jobs, nextQuery, resp);
});
}
/**
* Create a reference to an existing job.
*
* @param {string} id 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.
* @returns {Job}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const myExistingJob = bigquery.job('job-id');
*/
job(id, options) {
if (this.location) {
options = extend({ location: this.location }, options);
}
return new job_1.Job(this, id, options);
}
/**
* Run a query scoped to your project. For manual pagination please refer to
* {@link BigQuery#createQueryJob}.
*
* @see [Jobs: query API Documentation]{@link https://cloud.google.com/bigquery/docs/reference/v2/jobs/query}
*
* @param {string|object} query A string SQL query or configuration object.
* For all available options, see
* [Jobs: query request
* body](https://cloud.google.com/bigquery/docs/reference/v2/jobs/query#request-body).
* @param {string} [query.location] The geographic location of the job.
* Required except for US and EU.
* @param {string} [query.jobId] Custom id for the underlying job.
* @param {string} [query.jobPrefix] Prefix to apply to the underlying job id.
* @param {object|Array<*>} query.params For positional SQL parameters, provide
* an array of values. For named SQL parameters, provide an object which
* maps each named parameter to its value. The supported types are
* integers, floats, {@link BigQuery#date} objects, {@link BigQuery#datetime}
* objects, {@link BigQuery#time} objects, {@link BigQuery#timestamp}
* objects, Strings, Booleans, and Objects.
* @param {string} query.query A query string, following the BigQuery query
* syntax, of the query to execute.
* @param {boolean} [query.useLegacySql=false] Option to use legacy sql syntax.
* @param {object} [options] Configuration object for query results.
* @param {number} [options.maxResults] Maximum number of results to read.
* @param {number} [options.timeoutMs] How long to wait for the query to
* complete, in milliseconds, before returning. Default is to return
* immediately. If the timeout passes before the job completes, the
* request will fail with a `TIMEOUT` error.
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request
* @param {array} callback.rows The list of results from your query.
* @returns {Promise}
*
* @example
* const {BigQuery} = require('@google-cloud/bigquery');
* const bigquery = new BigQuery();
*
* const query = 'SELECT url FROM `publicdata.samples.github_nested` LIMIT
* 100';
*
* bigquery.query(query, function(err, rows) {
* if (!err) {
* // rows is an array of results.
* }
* });
*
* //-
* // Positional SQL parameters are supported.
* //-
* bigquery.query({
* query: [
* 'SELECT url',
* 'FROM `publicdata.samples.github_nested`',
* 'WHERE repository.owner = ?'
* ].join(' '),
*
* params: [
* 'google'
* ]
* }, function(err, rows) {});
*
* //-
* // Or if you prefer to name them, that's also supported.
* //-
* bigquery.query({
* query: [
* 'SELECT url',
* 'FROM `publicdata.samples.github_nested`',
* 'WHERE repository.owner = @owner'
* ].join(' '),
* params: {
* owner: 'google'
* }
* }, function(err, rows) {});
*
* //-
* // If you need to use a `DATE`, `DATETIME`, `TIME`, or `TIMESTAMP` type in
* // your query, see {@link BigQuery#date}, {@link BigQuery#datetime},
* // {@link BigQuery#time}, and {@link BigQuery#timestamp}.
* //-
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* bigquery.query(query).then(function(data) {
* const rows = data[0];
* });
*/
query(query, optionsOrCallback, cb) {
let options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
this.createQueryJob(query, (err, job, resp) => {
if (err) {
callback(err, null, resp);
return;
}
if (typeof query === 'object' && query.dryRun) {
callback(null, [], resp);
return;
}
// The Job is important for the `queryAsStream_` method, so a new query
// isn't created each time results are polled for.
options = extend({ job }, options);
job.getQueryResults(options, callback);
});
}
/**
* This method will be called by `createQueryStream()`. It is required to
* properly set the `autoPaginate` option value.
*
* @private
*/
queryAsStream_(query, callback) {
if (query.job) {
query.job.getQueryResults(query, callback);
return;
}
this.query(query, { autoPaginate: false }, callback);
}
}
exports.BigQuery = BigQuery;
/*! Developer Documentation
*
* These methods can be auto-paginated.
*/
paginator_1.paginator.extend(BigQuery, ['getDatasets', 'getJobs']);
/*! Developer Documentation
*
* All async methods (except for streams) will return a Promise in the event
* that a callback is omitted.
*/
promisify_1.promisifyAll(BigQuery, {
exclude: [
'dataset',
'date',
'datetime',
'geography',
'job',
'time',
'timestamp',
],
});
/**
* Date class for BigQuery.
*/
class BigQueryDate {
constructor(value) {
if (typeof value === 'object') {
value = BigQuery.datetime(value).value;
}
this.value = value;
}
}
exports.BigQueryDate = BigQueryDate;
/**
* Geography class for BigQuery.
*/
class Geography {
constructor(value) {
this.value = value;
}
}
exports.Geography = Geography;
/**
* Timestamp class for BigQuery.
*/
class BigQueryTimestamp {
constructor(value) {
this.value = new Date(value).toJSON();
}
}
exports.BigQueryTimestamp = BigQueryTimestamp;
/**
* Datetime class for BigQuery.
*/
class BigQueryDatetime {
constructor(value) {
if (typeof value === 'object') {
let time;
if (value.hours) {
time = BigQuery.time(value).value;
}
value = format('{y}-{m}-{d}{time}', {
y: value.year,
m: value.month,
d: value.day,
time: time ? ' ' + time : '',
});
}
else {
value = value.replace(/^(.*)T(.*)Z$/, '$1 $2');
}
this.value = value;
}
}
exports.BigQueryDatetime = BigQueryDatetime;
/**
* Time class for BigQuery.
*/
class BigQueryTime {
constructor(value) {
if (typeof value === 'object') {
value = format('{h}:{m}:{s}{f}', {
h: value.hours,
m: value.minutes || 0,
s: value.seconds || 0,
f: is.defined(value.fractional) ? '.' + value.fractional : '',
});
}
this.value = value;
}
}
exports.BigQueryTime = BigQueryTime;
//# sourceMappingURL=bigquery.js.map