UNPKG

50.6 kBJavaScriptView Raw
1"use strict";
2/*!
3 * Copyright 2019 Google LLC
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.BigQueryInt = exports.BigQueryTime = exports.BigQueryDatetime = exports.BigQueryTimestamp = exports.Geography = exports.BigQueryDate = exports.BigQuery = exports.PROTOCOL_REGEX = void 0;
19const common_1 = require("@google-cloud/common");
20const paginator_1 = require("@google-cloud/paginator");
21const promisify_1 = require("@google-cloud/promisify");
22const arrify = require("arrify");
23const big_js_1 = require("big.js");
24const extend = require("extend");
25const is = require("is");
26const uuid = require("uuid");
27const dataset_1 = require("./dataset");
28const job_1 = require("./job");
29const table_1 = require("./table");
30exports.PROTOCOL_REGEX = /^(\w*):\/\//;
31/**
32 * @typedef {object} BigQueryOptions
33 * @property {string} [projectId] The project ID from the Google Developer's
34 * Console, e.g. 'grape-spaceship-123'. We will also check the environment
35 * variable `GCLOUD_PROJECT` for your project ID. If your app is running in
36 * an environment which supports {@link
37 * https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
38 * Application Default Credentials}, your project ID will be detected
39 * automatically.
40 * @property {string} [keyFilename] Full path to the a .json, .pem, or .p12 key
41 * downloaded from the Google Developers Console. If you provide a path to a
42 * JSON file, the `projectId` option above is not necessary. NOTE: .pem and
43 * .p12 require you to specify the `email` option as well.
44 * @property {string} [token] An OAUTH access token. If provided, we will not
45 * manage fetching, re-using, and re-minting access tokens.
46 * @property {string} [email] Account email address. Required when using a .pem
47 * or .p12 keyFilename.
48 * @property {object} [credentials] Credentials object.
49 * @property {string} [credentials.client_email]
50 * @property {string} [credentials.private_key]
51 * @property {Constructor} [promise] Custom promise module to use instead of
52 * native Promises.
53 * @property {string[]} [scopes] Additional OAuth scopes to use in requests. For
54 * example, to access an external data source, you may need the
55 * `https://www.googleapis.com/auth/drive.readonly` scope.
56 */
57/**
58 * In the following examples from this page and the other modules (`Dataset`,
59 * `Table`, etc.), we are going to be using a dataset from
60 * {@link http://goo.gl/f2SXcb| data.gov} of higher education institutions.
61 *
62 * We will create a table with the correct schema, import the public CSV file
63 * into that table, and query it for data.
64 *
65 * @class
66 *
67 * See {@link https://cloud.google.com/bigquery/what-is-bigquery| What is BigQuery?}
68 *
69 * @param {BigQueryOptions} options Constructor options.
70 *
71 * @example Install the client library with <a href="https://www.npmjs.com/">npm</a>:
72 * ```
73 * npm install @google-cloud/bigquery
74 *
75 * ```
76 * @example Import the client library
77 * ```
78 * const {BigQuery} = require('@google-cloud/bigquery');
79 *
80 * ```
81 * @example Create a client that uses <a href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application Default Credentials (ADC)</a>:
82 * ```
83 * const bigquery = new BigQuery();
84 *
85 * ```
86 * @example Create a client with <a href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit credentials</a>:
87 * ```
88 * const bigquery = new BigQuery({
89 * projectId: 'your-project-id',
90 * keyFilename: '/path/to/keyfile.json'
91 * });
92 *
93 * ```
94 * @example <caption>include:samples/quickstart.js</caption>
95 * region_tag:bigquery_quickstart
96 * Full quickstart example:
97 */
98class BigQuery extends common_1.Service {
99 constructor(options = {}) {
100 let apiEndpoint = 'https://bigquery.googleapis.com';
101 const EMULATOR_HOST = process.env.BIGQUERY_EMULATOR_HOST;
102 if (typeof EMULATOR_HOST === 'string') {
103 apiEndpoint = BigQuery.sanitizeEndpoint(EMULATOR_HOST);
104 }
105 if (options.apiEndpoint) {
106 apiEndpoint = BigQuery.sanitizeEndpoint(options.apiEndpoint);
107 }
108 options = Object.assign({}, options, {
109 apiEndpoint,
110 });
111 const baseUrl = EMULATOR_HOST || `${options.apiEndpoint}/bigquery/v2`;
112 const config = {
113 apiEndpoint: options.apiEndpoint,
114 baseUrl,
115 scopes: ['https://www.googleapis.com/auth/bigquery'],
116 packageJson: require('../../package.json'),
117 };
118 if (options.scopes) {
119 config.scopes = config.scopes.concat(options.scopes);
120 }
121 super(config, options);
122 this.location = options.location;
123 /**
124 * Run a query scoped to your project as a readable object stream.
125 *
126 * @method
127 * @param {object} query Configuration object. See {@link BigQuery.query} for a complete
128 * list of options.
129 *
130 * @example
131 * ```
132 * const {BigQuery} = require('@google-cloud/bigquery');
133 * const bigquery = new BigQuery();
134 *
135 * const query = 'SELECT url FROM `publicdata.samples.github_nested` LIMIT
136 * 100';
137 *
138 * bigquery.createQueryStream(query)
139 * .on('error', console.error)
140 * .on('data', function(row) {
141 * // row is a result from your query.
142 * })
143 * .on('end', function() {
144 * // All rows retrieved.
145 * });
146 *
147 * //-
148 * // If you anticipate many results, you can end a stream early to prevent
149 * // unnecessary processing and API requests.
150 * //-
151 * bigquery.createQueryStream(query)
152 * .on('data', function(row) {
153 * this.end();
154 * });
155 * ```
156 */
157 this.createQueryStream = paginator_1.paginator.streamify('queryAsStream_');
158 /**
159 * List all or some of the {@link Dataset} objects in your project as
160 * a readable object stream.
161 *
162 * @param {object} [options] Configuration object. See
163 * {@link BigQuery.getDatasets} for a complete list of options.
164 *
165 * @example
166 * ```
167 * const {BigQuery} = require('@google-cloud/bigquery');
168 * const bigquery = new BigQuery();
169 *
170 * bigquery.getDatasetsStream()
171 * .on('error', console.error)
172 * .on('data', function(dataset) {
173 * // dataset is a Dataset object.
174 * })
175 * .on('end', function() {
176 * // All datasets retrieved.
177 * });
178 *
179 * //-
180 * // If you anticipate many results, you can end a stream early to prevent
181 * // unnecessary processing and API requests.
182 * //-
183 * bigquery.getDatasetsStream()
184 * .on('data', function(dataset) {
185 * this.end();
186 * });
187 * ```
188 */
189 this.getDatasetsStream = paginator_1.paginator.streamify('getDatasets');
190 /**
191 * List all or some of the {@link Job} objects in your project as a
192 * readable object stream.
193 *
194 * @param {object} [options] Configuration object. See
195 * {@link BigQuery.getJobs} for a complete list of options.
196 *
197 * @example
198 * ```
199 * const {BigQuery} = require('@google-cloud/bigquery');
200 * const bigquery = new BigQuery();
201 *
202 * bigquery.getJobsStream()
203 * .on('error', console.error)
204 * .on('data', function(job) {
205 * // job is a Job object.
206 * })
207 * .on('end', function() {
208 * // All jobs retrieved.
209 * });
210 *
211 * //-
212 * // If you anticipate many results, you can end a stream early to prevent
213 * // unnecessary processing and API requests.
214 * //-
215 * bigquery.getJobsStream()
216 * .on('data', function(job) {
217 * this.end();
218 * });
219 * ```
220 */
221 this.getJobsStream = paginator_1.paginator.streamify('getJobs');
222 // Disable `prettyPrint` for better performance.
223 // https://github.com/googleapis/nodejs-bigquery/issues/858
224 this.interceptors.push({
225 request: (reqOpts) => {
226 return extend(true, {}, reqOpts, { qs: { prettyPrint: false } });
227 },
228 });
229 }
230 createQueryStream(options) {
231 // placeholder body, overwritten in constructor
232 return new paginator_1.ResourceStream({}, () => { });
233 }
234 getDatasetsStream(options) {
235 // placeholder body, overwritten in constructor
236 return new paginator_1.ResourceStream({}, () => { });
237 }
238 getJobsStream(options) {
239 // placeholder body, overwritten in constructor
240 return new paginator_1.ResourceStream({}, () => { });
241 }
242 static sanitizeEndpoint(url) {
243 if (!exports.PROTOCOL_REGEX.test(url)) {
244 url = `https://${url}`;
245 }
246 return url.replace(/\/+$/, ''); // Remove trailing slashes
247 }
248 /**
249 * Merge a rowset returned from the API with a table schema.
250 *
251 * @private
252 *
253 * @param {object} schema
254 * @param {array} rows
255 * @param {boolean|IntegerTypeCastOptions} wrapIntegers Wrap values of
256 * 'INT64' type in {@link BigQueryInt} objects.
257 * If a `boolean`, this will wrap values in {@link BigQueryInt} objects.
258 * If an `object`, this will return a value returned by
259 * `wrapIntegers.integerTypeCastFunction`.
260 * Please see {@link IntegerTypeCastOptions} for options descriptions.
261 * @param {array} selectedFields List of fields to return.
262 * If unspecified, all fields are returned.
263 * @returns Fields using their matching names from the table's schema.
264 */
265 static mergeSchemaWithRows_(schema, rows, wrapIntegers, selectedFields) {
266 var _a;
267 if (selectedFields && selectedFields.length > 0) {
268 const selectedFieldsArray = selectedFields.map(c => {
269 return c.split('.');
270 });
271 const currentFields = selectedFieldsArray.map(c => c.shift());
272 //filter schema fields based on selected fields.
273 schema.fields = (_a = schema.fields) === null || _a === void 0 ? void 0 : _a.filter(field => currentFields
274 .map(c => c.toLowerCase())
275 .indexOf(field.name.toLowerCase()) >= 0);
276 selectedFields = selectedFieldsArray
277 .filter(c => c.length > 0)
278 .map(c => c.join('.'));
279 }
280 return arrify(rows)
281 .map(mergeSchema)
282 .map(flattenRows);
283 function mergeSchema(row) {
284 return row.f.map((field, index) => {
285 const schemaField = schema.fields[index];
286 let value = field.v;
287 if (schemaField.mode === 'REPEATED') {
288 value = value.map(val => {
289 return convert(schemaField, val.v, wrapIntegers, selectedFields);
290 });
291 }
292 else {
293 value = convert(schemaField, value, wrapIntegers, selectedFields);
294 }
295 // eslint-disable-next-line @typescript-eslint/no-explicit-any
296 const fieldObject = {};
297 fieldObject[schemaField.name] = value;
298 return fieldObject;
299 });
300 }
301 function convert(schemaField,
302 // eslint-disable-next-line @typescript-eslint/no-explicit-any
303 value, wrapIntegers, selectedFields) {
304 if (is.null(value)) {
305 return value;
306 }
307 switch (schemaField.type) {
308 case 'BOOLEAN':
309 case 'BOOL': {
310 value = value.toLowerCase() === 'true';
311 break;
312 }
313 case 'BYTES': {
314 value = Buffer.from(value, 'base64');
315 break;
316 }
317 case 'FLOAT':
318 case 'FLOAT64': {
319 value = Number(value);
320 break;
321 }
322 case 'INTEGER':
323 case 'INT64': {
324 value = wrapIntegers
325 ? typeof wrapIntegers === 'object'
326 ? BigQuery.int({ integerValue: value, schemaFieldName: schemaField.name }, wrapIntegers).valueOf()
327 : BigQuery.int(value)
328 : Number(value);
329 break;
330 }
331 case 'NUMERIC': {
332 value = new big_js_1.Big(value);
333 break;
334 }
335 case 'BIGNUMERIC': {
336 value = new big_js_1.Big(value);
337 break;
338 }
339 case 'RECORD': {
340 value = BigQuery.mergeSchemaWithRows_(schemaField, value, wrapIntegers, selectedFields).pop();
341 break;
342 }
343 case 'DATE': {
344 value = BigQuery.date(value);
345 break;
346 }
347 case 'DATETIME': {
348 value = BigQuery.datetime(value);
349 break;
350 }
351 case 'TIME': {
352 value = BigQuery.time(value);
353 break;
354 }
355 case 'TIMESTAMP': {
356 value = BigQuery.timestamp(new Date(value * 1000));
357 break;
358 }
359 case 'GEOGRAPHY': {
360 value = BigQuery.geography(value);
361 break;
362 }
363 default:
364 break;
365 }
366 return value;
367 }
368 // eslint-disable-next-line @typescript-eslint/no-explicit-any
369 function flattenRows(rows) {
370 return rows.reduce((acc, row) => {
371 const key = Object.keys(row)[0];
372 acc[key] = row[key];
373 return acc;
374 }, {});
375 }
376 }
377 /**
378 * The `DATE` type represents a logical calendar date, independent of time
379 * zone. It does not represent a specific 24-hour time period. Rather, a given
380 * DATE value represents a different 24-hour period when interpreted in
381 * different time zones, and may represent a shorter or longer day during
382 * Daylight Savings Time transitions.
383 *
384 * @param {object|string} value The date. If a string, this should be in the
385 * format the API describes: `YYYY-[M]M-[D]D`.
386 * Otherwise, provide an object.
387 * @param {string|number} value.year Four digits.
388 * @param {string|number} value.month One or two digits.
389 * @param {string|number} value.day One or two digits.
390 *
391 * @example
392 * ```
393 * const {BigQuery} = require('@google-cloud/bigquery');
394 * const bigquery = new BigQuery();
395 * const date = bigquery.date('2017-01-01');
396 *
397 * //-
398 * // Alternatively, provide an object.
399 * //-
400 * const date2 = bigquery.date({
401 * year: 2017,
402 * month: 1,
403 * day: 1
404 * });
405 * ```
406 */
407 static date(value) {
408 return new BigQueryDate(value);
409 }
410 /**
411 * @param {object|string} value The date. If a string, this should be in the
412 * format the API describes: `YYYY-[M]M-[D]D`.
413 * Otherwise, provide an object.
414 * @param {string|number} value.year Four digits.
415 * @param {string|number} value.month One or two digits.
416 * @param {string|number} value.day One or two digits.
417 *
418 * @example
419 * ```
420 * const {BigQuery} = require('@google-cloud/bigquery');
421 * const date = BigQuery.date('2017-01-01');
422 *
423 * //-
424 * // Alternatively, provide an object.
425 * //-
426 * const date2 = BigQuery.date({
427 * year: 2017,
428 * month: 1,
429 * day: 1
430 * });
431 * ```
432 */
433 date(value) {
434 return BigQuery.date(value);
435 }
436 /**
437 * A `DATETIME` data type represents a point in time. Unlike a `TIMESTAMP`,
438 * this does not refer to an absolute instance in time. Instead, it is the
439 * civil time, or the time that a user would see on a watch or calendar.
440 *
441 * @method BigQuery.datetime
442 * @param {object|string} value The time. If a string, this should be in the
443 * format the API describes: `YYYY-[M]M-[D]D[ [H]H:[M]M:[S]S[.DDDDDD]]`.
444 * Otherwise, provide an object.
445 * @param {string|number} value.year Four digits.
446 * @param {string|number} value.month One or two digits.
447 * @param {string|number} value.day One or two digits.
448 * @param {string|number} [value.hours] One or two digits (`00` - `23`).
449 * @param {string|number} [value.minutes] One or two digits (`00` - `59`).
450 * @param {string|number} [value.seconds] One or two digits (`00` - `59`).
451 * @param {string|number} [value.fractional] Up to six digits for microsecond
452 * precision.
453 *
454 * @example
455 * ```
456 * const {BigQuery} = require('@google-cloud/bigquery');
457 * const datetime = BigQuery.datetime('2017-01-01 13:00:00');
458 *
459 * //-
460 * // Alternatively, provide an object.
461 * //-
462 * const datetime = BigQuery.datetime({
463 * year: 2017,
464 * month: 1,
465 * day: 1,
466 * hours: 14,
467 * minutes: 0,
468 * seconds: 0
469 * });
470 * ```
471 */
472 /**
473 * A `DATETIME` data type represents a point in time. Unlike a `TIMESTAMP`,
474 * this does not refer to an absolute instance in time. Instead, it is the
475 * civil time, or the time that a user would see on a watch or calendar.
476 *
477 * @param {object|string} value The time. If a string, this should be in the
478 * format the API describes: `YYYY-[M]M-[D]D[ [H]H:[M]M:[S]S[.DDDDDD]]`.
479 * Otherwise, provide an object.
480 * @param {string|number} value.year Four digits.
481 * @param {string|number} value.month One or two digits.
482 * @param {string|number} value.day One or two digits.
483 * @param {string|number} [value.hours] One or two digits (`00` - `23`).
484 * @param {string|number} [value.minutes] One or two digits (`00` - `59`).
485 * @param {string|number} [value.seconds] One or two digits (`00` - `59`).
486 * @param {string|number} [value.fractional] Up to six digits for microsecond
487 * precision.
488 *
489 * @example
490 * ```
491 * const {BigQuery} = require('@google-cloud/bigquery');
492 * const bigquery = new BigQuery();
493 * const datetime = bigquery.datetime('2017-01-01 13:00:00');
494 *
495 * //-
496 * // Alternatively, provide an object.
497 * //-
498 * const datetime = bigquery.datetime({
499 * year: 2017,
500 * month: 1,
501 * day: 1,
502 * hours: 14,
503 * minutes: 0,
504 * seconds: 0
505 * });
506 * ```
507 */
508 static datetime(value) {
509 return new BigQueryDatetime(value);
510 }
511 datetime(value) {
512 return BigQuery.datetime(value);
513 }
514 /**
515 * A `TIME` data type represents a time, independent of a specific date.
516 *
517 * @method BigQuery.time
518 * @param {object|string} value The time. If a string, this should be in the
519 * format the API describes: `[H]H:[M]M:[S]S[.DDDDDD]`. Otherwise, provide
520 * an object.
521 * @param {string|number} [value.hours] One or two digits (`00` - `23`).
522 * @param {string|number} [value.minutes] One or two digits (`00` - `59`).
523 * @param {string|number} [value.seconds] One or two digits (`00` - `59`).
524 * @param {string|number} [value.fractional] Up to six digits for microsecond
525 * precision.
526 *
527 * @example
528 * ```
529 * const {BigQuery} = require('@google-cloud/bigquery');
530 * const time = BigQuery.time('14:00:00'); // 2:00 PM
531 *
532 * //-
533 * // Alternatively, provide an object.
534 * //-
535 * const time = BigQuery.time({
536 * hours: 14,
537 * minutes: 0,
538 * seconds: 0
539 * });
540 * ```
541 */
542 /**
543 * A `TIME` data type represents a time, independent of a specific date.
544 *
545 * @param {object|string} value The time. If a string, this should be in the
546 * format the API describes: `[H]H:[M]M:[S]S[.DDDDDD]`. Otherwise, provide
547 * an object.
548 * @param {string|number} [value.hours] One or two digits (`00` - `23`).
549 * @param {string|number} [value.minutes] One or two digits (`00` - `59`).
550 * @param {string|number} [value.seconds] One or two digits (`00` - `59`).
551 * @param {string|number} [value.fractional] Up to six digits for microsecond
552 * precision.
553 *
554 * @example
555 * ```
556 * const {BigQuery} = require('@google-cloud/bigquery');
557 * const bigquery = new BigQuery();
558 * const time = bigquery.time('14:00:00'); // 2:00 PM
559 *
560 * //-
561 * // Alternatively, provide an object.
562 * //-
563 * const time = bigquery.time({
564 * hours: 14,
565 * minutes: 0,
566 * seconds: 0
567 * });
568 * ```
569 */
570 static time(value) {
571 return new BigQueryTime(value);
572 }
573 time(value) {
574 return BigQuery.time(value);
575 }
576 /**
577 * A timestamp represents an absolute point in time, independent of any time
578 * zone or convention such as Daylight Savings Time.
579 *
580 * @method BigQuery.timestamp
581 * @param {Date|string} value The time.
582 *
583 * @example
584 * ```
585 * const {BigQuery} = require('@google-cloud/bigquery');
586 * const timestamp = BigQuery.timestamp(new Date());
587 * ```
588 */
589 /**
590 * A timestamp represents an absolute point in time, independent of any time
591 * zone or convention such as Daylight Savings Time.
592 *
593 * @param {Date|string} value The time.
594 *
595 * @example
596 * ```
597 * const {BigQuery} = require('@google-cloud/bigquery');
598 * const bigquery = new BigQuery();
599 * const timestamp = bigquery.timestamp(new Date());
600 * ```
601 */
602 static timestamp(value) {
603 return new BigQueryTimestamp(value);
604 }
605 timestamp(value) {
606 return BigQuery.timestamp(value);
607 }
608 /**
609 * A BigQueryInt wraps 'INT64' values. Can be used to maintain precision.
610 *
611 * @param {string|number|IntegerTypeCastValue} value The INT64 value to convert.
612 * @param {IntegerTypeCastOptions} typeCastOptions Configuration to convert
613 * value. Must provide an `integerTypeCastFunction` to handle conversion.
614 * @returns {BigQueryInt}
615 *
616 * @example
617 * ```
618 * const {BigQuery} = require('@google-cloud/bigquery');
619 * const bigquery = new BigQuery();
620 *
621 * const largeIntegerValue = Number.MAX_SAFE_INTEGER + 1;
622 *
623 * const options = {
624 * integerTypeCastFunction: value => value.split(),
625 * };
626 *
627 * const bqInteger = bigquery.int(largeIntegerValue, options);
628 *
629 * const customValue = bqInteger.valueOf();
630 * // customValue is the value returned from your `integerTypeCastFunction`.
631 * ```
632 */
633 static int(value, typeCastOptions) {
634 return new BigQueryInt(value, typeCastOptions);
635 }
636 int(value, typeCastOptions) {
637 return BigQuery.int(value, typeCastOptions);
638 }
639 /**
640 * A geography value represents a surface area on the Earth
641 * in Well-known Text (WKT) format.
642 *
643 * @param {string} value The geospatial data.
644 *
645 * @example
646 * ```
647 * const {BigQuery} = require('@google-cloud/bigquery');
648 * const bigquery = new BigQuery();
649 * const geography = bigquery.geography('POINT(1, 2)');
650 * ```
651 */
652 static geography(value) {
653 return new Geography(value);
654 }
655 geography(value) {
656 return BigQuery.geography(value);
657 }
658 /**
659 * Convert an INT64 value to Number.
660 *
661 * @private
662 * @param {object} value The INT64 value to convert.
663 */
664 static decodeIntegerValue_(value) {
665 const num = Number(value.integerValue);
666 if (!Number.isSafeInteger(num)) {
667 throw new Error('We attempted to return all of the numeric values, but ' +
668 (value.schemaFieldName ? value.schemaFieldName + ' ' : '') +
669 'value ' +
670 value.integerValue +
671 " is out of bounds of 'Number.MAX_SAFE_INTEGER'.\n" +
672 "To prevent this error, please consider passing 'options.wrapNumbers' as\n" +
673 '{\n' +
674 ' integerTypeCastFunction: provide <your_custom_function>\n' +
675 ' fields: optionally specify field name(s) to be custom casted\n' +
676 '}\n');
677 }
678 return num;
679 }
680 /**
681 * Return a value's provided type.
682 *
683 * @private
684 *
685 * @throws {error} If the type provided is invalid.
686 *
687 * See {@link https://cloud.google.com/bigquery/data-types| Data Type}
688 *
689 * @param {*} providedType The type.
690 * @returns {string} The valid type provided.
691 */
692 static getTypeDescriptorFromProvidedType_(providedType) {
693 // The list of types can be found in src/types.d.ts
694 const VALID_TYPES = [
695 'DATE',
696 'DATETIME',
697 'TIME',
698 'TIMESTAMP',
699 'BYTES',
700 'NUMERIC',
701 'BIGNUMERIC',
702 'BOOL',
703 'INT64',
704 'FLOAT64',
705 'STRING',
706 'GEOGRAPHY',
707 'ARRAY',
708 'STRUCT',
709 ];
710 if (is.array(providedType)) {
711 providedType = providedType;
712 return {
713 type: 'ARRAY',
714 arrayType: BigQuery.getTypeDescriptorFromProvidedType_(providedType[0]),
715 };
716 }
717 else if (is.object(providedType)) {
718 return {
719 type: 'STRUCT',
720 structTypes: Object.keys(providedType).map(prop => {
721 return {
722 name: prop,
723 type: BigQuery.getTypeDescriptorFromProvidedType_(providedType[prop]),
724 };
725 }),
726 };
727 }
728 providedType = providedType.toUpperCase();
729 if (!VALID_TYPES.includes(providedType)) {
730 throw new Error(`Invalid type provided: "${providedType}"`);
731 }
732 return { type: providedType.toUpperCase() };
733 }
734 /**
735 * Detect a value's type.
736 *
737 * @private
738 *
739 * @throws {error} If the type could not be detected.
740 *
741 * See {@link https://cloud.google.com/bigquery/data-types| Data Type}
742 *
743 * @param {*} value The value.
744 * @returns {string} The type detected from the value.
745 */
746 static getTypeDescriptorFromValue_(value) {
747 let typeName;
748 if (value === null) {
749 throw new Error("Parameter types must be provided for null values via the 'types' field in query options.");
750 }
751 if (value instanceof BigQueryDate) {
752 typeName = 'DATE';
753 }
754 else if (value instanceof BigQueryDatetime) {
755 typeName = 'DATETIME';
756 }
757 else if (value instanceof BigQueryTime) {
758 typeName = 'TIME';
759 }
760 else if (value instanceof BigQueryTimestamp) {
761 typeName = 'TIMESTAMP';
762 }
763 else if (value instanceof Buffer) {
764 typeName = 'BYTES';
765 }
766 else if (value instanceof big_js_1.Big) {
767 if (value.c.length - value.e >= 10) {
768 typeName = 'BIGNUMERIC';
769 }
770 else {
771 typeName = 'NUMERIC';
772 }
773 }
774 else if (value instanceof BigQueryInt) {
775 typeName = 'INT64';
776 }
777 else if (value instanceof Geography) {
778 typeName = 'GEOGRAPHY';
779 }
780 else if (Array.isArray(value)) {
781 if (value.length === 0) {
782 throw new Error("Parameter types must be provided for empty arrays via the 'types' field in query options.");
783 }
784 return {
785 type: 'ARRAY',
786 arrayType: BigQuery.getTypeDescriptorFromValue_(value[0]),
787 };
788 }
789 else if (is.boolean(value)) {
790 typeName = 'BOOL';
791 }
792 else if (is.number(value)) {
793 typeName = value % 1 === 0 ? 'INT64' : 'FLOAT64';
794 }
795 else if (is.object(value)) {
796 return {
797 type: 'STRUCT',
798 structTypes: Object.keys(value).map(prop => {
799 return {
800 name: prop,
801 // eslint-disable-next-line @typescript-eslint/no-explicit-any
802 type: BigQuery.getTypeDescriptorFromValue_(value[prop]),
803 };
804 }),
805 };
806 }
807 else if (is.string(value)) {
808 typeName = 'STRING';
809 }
810 if (!typeName) {
811 throw new Error([
812 'This value could not be translated to a BigQuery data type.',
813 value,
814 ].join('\n'));
815 }
816 return {
817 type: typeName,
818 };
819 }
820 /**
821 * Convert a value into a `queryParameter` object.
822 *
823 * @private
824 *
825 * See {@link https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#request-body| Jobs.query API Reference Docs (see `queryParameters`)}
826 *
827 * @param {*} value The value.
828 * @param {string|ProvidedTypeStruct|ProvidedTypeArray} providedType Provided
829 * query parameter type.
830 * @returns {object} A properly-formed `queryParameter` object.
831 */
832 static valueToQueryParameter_(
833 // eslint-disable-next-line @typescript-eslint/no-explicit-any
834 value, providedType) {
835 if (is.date(value)) {
836 value = BigQuery.timestamp(value);
837 }
838 let parameterType;
839 if (providedType) {
840 parameterType = BigQuery.getTypeDescriptorFromProvidedType_(providedType);
841 }
842 else {
843 parameterType = BigQuery.getTypeDescriptorFromValue_(value);
844 }
845 const queryParameter = { parameterType, parameterValue: {} };
846 const typeName = queryParameter.parameterType.type;
847 if (typeName === 'ARRAY') {
848 queryParameter.parameterValue.arrayValues = value.map(itemValue => {
849 const value = BigQuery._getValue(itemValue, parameterType.arrayType);
850 if (is.object(value) || is.array(value)) {
851 if (is.array(providedType)) {
852 providedType = providedType;
853 return BigQuery.valueToQueryParameter_(value, providedType[0])
854 .parameterValue;
855 }
856 else {
857 return BigQuery.valueToQueryParameter_(value).parameterValue;
858 }
859 }
860 return { value };
861 });
862 }
863 else if (typeName === 'STRUCT') {
864 queryParameter.parameterValue.structValues = Object.keys(value).reduce((structValues, prop) => {
865 let nestedQueryParameter;
866 if (providedType) {
867 nestedQueryParameter = BigQuery.valueToQueryParameter_(value[prop], providedType[prop]);
868 }
869 else {
870 nestedQueryParameter = BigQuery.valueToQueryParameter_(value[prop]);
871 }
872 // eslint-disable-next-line @typescript-eslint/no-explicit-any
873 structValues[prop] = nestedQueryParameter.parameterValue;
874 return structValues;
875 }, {});
876 }
877 else {
878 queryParameter.parameterValue.value = BigQuery._getValue(value, parameterType);
879 }
880 return queryParameter;
881 }
882 // eslint-disable-next-line @typescript-eslint/no-explicit-any
883 static _getValue(value, type) {
884 if (value === null) {
885 return null;
886 }
887 if (value.type)
888 type = value;
889 return BigQuery._isCustomType(type) ? value.value : value;
890 }
891 static _isCustomType({ type }) {
892 return (type.indexOf('TIME') > -1 ||
893 type.indexOf('DATE') > -1 ||
894 type.indexOf('GEOGRAPHY') > -1 ||
895 type.indexOf('BigQueryInt') > -1);
896 }
897 createDataset(id, optionsOrCallback, cb) {
898 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
899 const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
900 this.request({
901 method: 'POST',
902 uri: '/datasets',
903 json: extend(true, {
904 location: this.location,
905 }, options, {
906 datasetReference: {
907 datasetId: id,
908 },
909 }),
910 }, (err, resp) => {
911 if (err) {
912 callback(err, null, resp);
913 return;
914 }
915 const dataset = this.dataset(id);
916 dataset.metadata = resp;
917 callback(null, dataset, resp);
918 });
919 }
920 createQueryJob(opts, callback) {
921 const options = typeof opts === 'object' ? opts : { query: opts };
922 if ((!options || !options.query) && !options.pageToken) {
923 throw new Error('A SQL query string is required.');
924 }
925 // eslint-disable-next-line @typescript-eslint/no-explicit-any
926 const query = extend(true, {
927 useLegacySql: false,
928 }, options);
929 if (options.destination) {
930 if (!(options.destination instanceof table_1.Table)) {
931 throw new Error('Destination must be a Table object.');
932 }
933 query.destinationTable = {
934 datasetId: options.destination.dataset.id,
935 projectId: options.destination.dataset.bigQuery.projectId,
936 tableId: options.destination.id,
937 };
938 delete query.destination;
939 }
940 if (query.params) {
941 query.parameterMode = is.array(query.params) ? 'positional' : 'named';
942 if (query.parameterMode === 'named') {
943 query.queryParameters = [];
944 // tslint:disable-next-line forin
945 for (const namedParameter in query.params) {
946 const value = query.params[namedParameter];
947 let queryParameter;
948 if (query.types) {
949 if (!is.object(query.types)) {
950 throw new Error('Provided types must match the value type passed to `params`');
951 }
952 if (query.types[namedParameter]) {
953 queryParameter = BigQuery.valueToQueryParameter_(value, query.types[namedParameter]);
954 }
955 else {
956 queryParameter = BigQuery.valueToQueryParameter_(value);
957 }
958 }
959 else {
960 queryParameter = BigQuery.valueToQueryParameter_(value);
961 }
962 queryParameter.name = namedParameter;
963 query.queryParameters.push(queryParameter);
964 }
965 }
966 else {
967 query.queryParameters = [];
968 if (query.types) {
969 if (!is.array(query.types)) {
970 throw new Error('Provided types must match the value type passed to `params`');
971 }
972 if (query.params.length !== query.types.length) {
973 throw new Error('Incorrect number of parameter types provided.');
974 }
975 query.params.forEach((value, i) => {
976 const queryParameter = BigQuery.valueToQueryParameter_(value, query.types[i]);
977 query.queryParameters.push(queryParameter);
978 });
979 }
980 else {
981 query.params.forEach((value) => {
982 const queryParameter = BigQuery.valueToQueryParameter_(value);
983 query.queryParameters.push(queryParameter);
984 });
985 }
986 }
987 delete query.params;
988 }
989 // eslint-disable-next-line @typescript-eslint/no-explicit-any
990 const reqOpts = {
991 configuration: {
992 query,
993 },
994 };
995 if (typeof query.jobTimeoutMs === 'number') {
996 reqOpts.configuration.jobTimeoutMs = query.jobTimeoutMs;
997 delete query.jobTimeoutMs;
998 }
999 if (query.dryRun) {
1000 reqOpts.configuration.dryRun = query.dryRun;
1001 delete query.dryRun;
1002 }
1003 if (query.labels) {
1004 reqOpts.configuration.labels = query.labels;
1005 delete query.labels;
1006 }
1007 if (query.jobPrefix) {
1008 reqOpts.jobPrefix = query.jobPrefix;
1009 delete query.jobPrefix;
1010 }
1011 if (query.location) {
1012 reqOpts.location = query.location;
1013 delete query.location;
1014 }
1015 if (query.jobId) {
1016 reqOpts.jobId = query.jobId;
1017 delete query.jobId;
1018 }
1019 this.createJob(reqOpts, callback);
1020 }
1021 createJob(options, callback) {
1022 var _a;
1023 const JOB_ID_PROVIDED = typeof options.jobId !== 'undefined';
1024 const DRY_RUN = ((_a = options.configuration) === null || _a === void 0 ? void 0 : _a.dryRun) ? options.configuration.dryRun
1025 : false;
1026 const reqOpts = Object.assign({}, options);
1027 let jobId = JOB_ID_PROVIDED ? reqOpts.jobId : uuid.v4();
1028 if (reqOpts.jobId) {
1029 delete reqOpts.jobId;
1030 }
1031 if (reqOpts.jobPrefix) {
1032 jobId = reqOpts.jobPrefix + jobId;
1033 delete reqOpts.jobPrefix;
1034 }
1035 reqOpts.jobReference = {
1036 projectId: this.projectId,
1037 jobId,
1038 location: this.location,
1039 };
1040 if (options.location) {
1041 reqOpts.jobReference.location = options.location;
1042 delete reqOpts.location;
1043 }
1044 const job = this.job(jobId, {
1045 location: reqOpts.jobReference.location,
1046 });
1047 this.request({
1048 method: 'POST',
1049 uri: '/jobs',
1050 json: reqOpts,
1051 }, async (err, resp) => {
1052 const ALREADY_EXISTS_CODE = 409;
1053 if (err) {
1054 if (err.code === ALREADY_EXISTS_CODE &&
1055 !JOB_ID_PROVIDED &&
1056 !DRY_RUN) {
1057 // The last insert attempt flaked, but the API still processed the
1058 // request and created the job. Because of our "autoRetry" feature,
1059 // we tried the request again, which tried to create it again,
1060 // unnecessarily. We will get the job's metadata and treat it as if
1061 // it just came back from the create call.
1062 err = null;
1063 [resp] = await job.getMetadata();
1064 }
1065 else {
1066 callback(err, null, resp);
1067 return;
1068 }
1069 }
1070 if (resp.status.errors) {
1071 err = new common_1.util.ApiError({
1072 errors: resp.status.errors,
1073 response: resp,
1074 });
1075 }
1076 // Update the location with the one used by the API.
1077 job.location = resp.jobReference.location;
1078 job.metadata = resp;
1079 callback(err, job, resp);
1080 });
1081 }
1082 /**
1083 * Create a reference to a dataset.
1084 *
1085 * @param {string} id ID of the dataset.
1086 * @param {object} [options] Dataset options.
1087 * @param {string} [options.location] The geographic location of the dataset.
1088 * Required except for US and EU.
1089 *
1090 * @example
1091 * ```
1092 * const {BigQuery} = require('@google-cloud/bigquery');
1093 * const bigquery = new BigQuery();
1094 * const dataset = bigquery.dataset('higher_education');
1095 * ```
1096 */
1097 dataset(id, options) {
1098 if (typeof id !== 'string') {
1099 throw new TypeError('A dataset ID is required.');
1100 }
1101 if (this.location) {
1102 options = extend({ location: this.location }, options);
1103 }
1104 return new dataset_1.Dataset(this, id, options);
1105 }
1106 getDatasets(optionsOrCallback, cb) {
1107 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
1108 const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
1109 this.request({
1110 uri: '/datasets',
1111 qs: options,
1112 }, (err, resp) => {
1113 if (err) {
1114 callback(err, null, null, resp);
1115 return;
1116 }
1117 let nextQuery = null;
1118 if (resp.nextPageToken) {
1119 nextQuery = Object.assign({}, options, {
1120 pageToken: resp.nextPageToken,
1121 });
1122 }
1123 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1124 const datasets = (resp.datasets || []).map((dataset) => {
1125 const ds = this.dataset(dataset.datasetReference.datasetId, {
1126 location: dataset.location,
1127 });
1128 ds.metadata = dataset;
1129 return ds;
1130 });
1131 callback(null, datasets, nextQuery, resp);
1132 });
1133 }
1134 getJobs(optionsOrCallback, cb) {
1135 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
1136 const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
1137 this.request({
1138 uri: '/jobs',
1139 qs: options,
1140 useQuerystring: true,
1141 }, (err, resp) => {
1142 if (err) {
1143 callback(err, null, null, resp);
1144 return;
1145 }
1146 let nextQuery = null;
1147 if (resp.nextPageToken) {
1148 nextQuery = Object.assign({}, options, {
1149 pageToken: resp.nextPageToken,
1150 });
1151 }
1152 const jobs = (resp.jobs || []).map((jobObject) => {
1153 const job = this.job(jobObject.jobReference.jobId, {
1154 location: jobObject.jobReference.location,
1155 });
1156 job.metadata = jobObject;
1157 return job;
1158 });
1159 callback(null, jobs, nextQuery, resp);
1160 });
1161 }
1162 /**
1163 * Create a reference to an existing job.
1164 *
1165 * @param {string} id ID of the job.
1166 * @param {object} [options] Configuration object.
1167 * @param {string} [options.location] The geographic location of the job.
1168 * Required except for US and EU.
1169 *
1170 * @example
1171 * ```
1172 * const {BigQuery} = require('@google-cloud/bigquery');
1173 * const bigquery = new BigQuery();
1174 *
1175 * const myExistingJob = bigquery.job('job-id');
1176 * ```
1177 */
1178 job(id, options) {
1179 if (this.location) {
1180 options = extend({ location: this.location }, options);
1181 }
1182 return new job_1.Job(this, id, options);
1183 }
1184 query(query, optionsOrCallback, cb) {
1185 let options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
1186 const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
1187 this.createQueryJob(query, (err, job, resp) => {
1188 if (err) {
1189 callback(err, null, resp);
1190 return;
1191 }
1192 if (typeof query === 'object' && query.dryRun) {
1193 callback(null, [], resp);
1194 return;
1195 }
1196 // The Job is important for the `queryAsStream_` method, so a new query
1197 // isn't created each time results are polled for.
1198 options = extend({ job }, options);
1199 job.getQueryResults(options, callback);
1200 });
1201 }
1202 /**
1203 * This method will be called by `createQueryStream()`. It is required to
1204 * properly set the `autoPaginate` option value.
1205 *
1206 * @private
1207 */
1208 queryAsStream_(query, callback) {
1209 if (query.job) {
1210 query.job.getQueryResults(query, callback);
1211 return;
1212 }
1213 const { location, maxResults, pageToken, wrapIntegers } = query;
1214 const opts = {
1215 location,
1216 maxResults,
1217 pageToken,
1218 wrapIntegers,
1219 autoPaginate: false,
1220 };
1221 delete query.location;
1222 delete query.maxResults;
1223 delete query.pageToken;
1224 delete query.wrapIntegers;
1225 this.query(query, opts, callback);
1226 }
1227}
1228exports.BigQuery = BigQuery;
1229/*! Developer Documentation
1230 *
1231 * These methods can be auto-paginated.
1232 */
1233paginator_1.paginator.extend(BigQuery, ['getDatasets', 'getJobs']);
1234/*! Developer Documentation
1235 *
1236 * All async methods (except for streams) will return a Promise in the event
1237 * that a callback is omitted.
1238 */
1239promisify_1.promisifyAll(BigQuery, {
1240 exclude: [
1241 'dataset',
1242 'date',
1243 'datetime',
1244 'geography',
1245 'int',
1246 'job',
1247 'time',
1248 'timestamp',
1249 ],
1250});
1251/**
1252 * Date class for BigQuery.
1253 */
1254class BigQueryDate {
1255 constructor(value) {
1256 if (typeof value === 'object') {
1257 value = BigQuery.datetime(value).value;
1258 }
1259 this.value = value;
1260 }
1261}
1262exports.BigQueryDate = BigQueryDate;
1263/**
1264 * Geography class for BigQuery.
1265 */
1266class Geography {
1267 constructor(value) {
1268 this.value = value;
1269 }
1270}
1271exports.Geography = Geography;
1272/**
1273 * Timestamp class for BigQuery.
1274 */
1275class BigQueryTimestamp {
1276 constructor(value) {
1277 this.value = new Date(value).toJSON();
1278 }
1279}
1280exports.BigQueryTimestamp = BigQueryTimestamp;
1281/**
1282 * Datetime class for BigQuery.
1283 */
1284class BigQueryDatetime {
1285 constructor(value) {
1286 if (typeof value === 'object') {
1287 let time;
1288 if (value.hours) {
1289 time = BigQuery.time(value).value;
1290 }
1291 const y = value.year;
1292 const m = value.month;
1293 const d = value.day;
1294 time = time ? ' ' + time : '';
1295 value = `${y}-${m}-${d}${time}`;
1296 }
1297 else {
1298 value = value.replace(/^(.*)T(.*)Z$/, '$1 $2');
1299 }
1300 this.value = value;
1301 }
1302}
1303exports.BigQueryDatetime = BigQueryDatetime;
1304/**
1305 * Time class for BigQuery.
1306 */
1307class BigQueryTime {
1308 constructor(value) {
1309 if (typeof value === 'object') {
1310 const h = value.hours;
1311 const m = value.minutes || 0;
1312 const s = value.seconds || 0;
1313 const f = is.defined(value.fractional) ? '.' + value.fractional : '';
1314 value = `${h}:${m}:${s}${f}`;
1315 }
1316 this.value = value;
1317 }
1318}
1319exports.BigQueryTime = BigQueryTime;
1320/**
1321 * Build a BigQueryInt object. For long integers, a string can be provided.
1322 *
1323 * @class
1324 * @param {string|number|IntegerTypeCastValue} value The 'INT64' value.
1325 * @param {object} [typeCastOptions] Configuration to convert
1326 * values of 'INT64' type to a custom value. Must provide an
1327 * `integerTypeCastFunction` to handle conversion.
1328 * @param {function} typeCastOptions.integerTypeCastFunction A custom user
1329 * provided function to convert value.
1330 * @param {string|string[]} [typeCastOptions.fields] Schema field
1331 * names to be converted using `integerTypeCastFunction`.
1332 *
1333 * @example
1334 * ```
1335 * const {BigQuery} = require('@google-cloud/bigquery');
1336 * const bigquery = new BigQuery();
1337 * const anInt = bigquery.int(7);
1338 * ```
1339 */
1340class BigQueryInt extends Number {
1341 constructor(value, typeCastOptions) {
1342 super(typeof value === 'object' ? value.integerValue : value);
1343 this._schemaFieldName =
1344 typeof value === 'object' ? value.schemaFieldName : undefined;
1345 this.value =
1346 typeof value === 'object'
1347 ? value.integerValue.toString()
1348 : value.toString();
1349 this.type = 'BigQueryInt';
1350 if (typeCastOptions) {
1351 if (typeof typeCastOptions.integerTypeCastFunction !== 'function') {
1352 throw new Error('integerTypeCastFunction is not a function or was not provided.');
1353 }
1354 const typeCastFields = typeCastOptions.fields
1355 ? arrify(typeCastOptions.fields)
1356 : undefined;
1357 let customCast = true;
1358 if (typeCastFields) {
1359 customCast = this._schemaFieldName
1360 ? typeCastFields.includes(this._schemaFieldName)
1361 ? true
1362 : false
1363 : false;
1364 }
1365 customCast &&
1366 (this.typeCastFunction = typeCastOptions.integerTypeCastFunction);
1367 }
1368 }
1369 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1370 valueOf() {
1371 const shouldCustomCast = this.typeCastFunction ? true : false;
1372 if (shouldCustomCast) {
1373 try {
1374 return this.typeCastFunction(this.value);
1375 }
1376 catch (error) {
1377 error.message = `integerTypeCastFunction threw an error:\n\n - ${error.message}`;
1378 throw error;
1379 }
1380 }
1381 else {
1382 // return this.value;
1383 return BigQuery.decodeIntegerValue_({
1384 integerValue: this.value,
1385 schemaFieldName: this._schemaFieldName,
1386 });
1387 }
1388 }
1389 toJSON() {
1390 return { type: this.type, value: this.value };
1391 }
1392}
1393exports.BigQueryInt = BigQueryInt;
1394//# sourceMappingURL=bigquery.js.map
\No newline at end of file