diff --git a/samples/listDatasets.js b/samples/listDatasets.js index 961d6ab4..0f5b8374 100644 --- a/samples/listDatasets.js +++ b/samples/listDatasets.js @@ -14,17 +14,22 @@ 'use strict'; -function main() { +function main(projectId) { // [START bigquery_list_datasets] // Import the Google Cloud client library const {BigQuery} = require('@google-cloud/bigquery'); const bigquery = new BigQuery(); async function listDatasets() { - // Lists all datasets in current GCP project. + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = "my_project_id"; - // Lists all datasets in the specified project - const [datasets] = await bigquery.getDatasets(); + // Lists all datasets in the specified project. + // If projectId is not specified, this method will take + // the projectId from the authenticated BigQuery Client. + const [datasets] = await bigquery.getDatasets({projectId}); console.log('Datasets:'); datasets.forEach(dataset => console.log(dataset.id)); } diff --git a/samples/test/datasets.test.js b/samples/test/datasets.test.js index fd4aecd1..cc0e1b2c 100644 --- a/samples/test/datasets.test.js +++ b/samples/test/datasets.test.js @@ -98,6 +98,12 @@ describe('Datasets', () => { assert.match(output, new RegExp(datasetId)); }); + it('should list datasets on a different project', async () => { + const output = execSync('node listDatasets.js bigquery-public-data'); + assert.match(output, /Datasets:/); + assert.match(output, new RegExp('usa_names')); + }); + it('should retrieve a dataset if it exists', async () => { const output = execSync(`node getDataset.js ${datasetId}`); assert.include(output, 'Dataset:'); diff --git a/src/bigquery.ts b/src/bigquery.ts index b9c58a39..c304a2b0 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -125,10 +125,15 @@ export type QueryStreamOptions = { wrapIntegers?: boolean | IntegerTypeCastOptions; parseJSON?: boolean; }; -export type DatasetResource = bigquery.IDataset; +export type DatasetResource = bigquery.IDataset & { + projectId?: string; +}; export type ValueType = bigquery.IQueryParameterType; -export type GetDatasetsOptions = PagedRequest; +export type GetDatasetsOptions = PagedRequest & { + projectId?: string; +}; + export type DatasetsResponse = PagedResponse< Dataset, GetDatasetsOptions, @@ -1257,35 +1262,36 @@ export class BigQuery extends Service { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; - this.request( - { - method: 'POST', - uri: '/datasets', - json: extend( - true, - { - location: this.location, + const reqOpts: DecorateRequestOptions = { + method: 'POST', + uri: '/datasets', + json: extend( + true, + { + location: this.location, + }, + options, + { + datasetReference: { + datasetId: id, }, - options, - { - datasetReference: { - datasetId: id, - }, - } - ), - }, - (err, resp) => { - if (err) { - callback!(err, null, resp); - return; } + ), + }; + if (options.projectId) { + reqOpts.projectId = options.projectId; + } + this.request(reqOpts, (err, resp) => { + if (err) { + callback!(err, null, resp); + return; + } - const dataset = this.dataset(id); - dataset.metadata = resp; + const dataset = this.dataset(id, options); + dataset.metadata = resp; - callback!(null, dataset, resp); - } - ); + callback!(null, dataset, resp); + }); } /** @@ -1664,6 +1670,7 @@ export class BigQuery extends Service { * * @param {string} id ID of the dataset. * @param {object} [options] Dataset options. + * @param {string} [options.projectId] The GCP project ID. * @param {string} [options.location] The geographic location of the dataset. * Required except for US and EU. * @@ -1686,12 +1693,13 @@ export class BigQuery extends Service { } /** - * List all or some of the datasets in your project. + * List all or some of the datasets in a project. * * See {@link https://cloud.google.com/bigquery/docs/reference/v2/datasets/list| Datasets: list API Documentation} * * @param {object} [options] Configuration object. * @param {boolean} [options.all] List all datasets, including hidden ones. + * @param {string} [options.projectId] The GCP project ID. * @param {boolean} [options.autoPaginate] Have pagination handled automatically. * Default: true. * @param {number} [options.maxApiCalls] Maximum number of API calls to make. @@ -1746,40 +1754,45 @@ export class BigQuery extends Service { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; - this.request( - { - uri: '/datasets', - qs: options, - }, - (err, resp) => { - if (err) { - callback!(err, null, null, resp); - return; - } + const reqOpts: DecorateRequestOptions = { + uri: '/datasets', + qs: options, + }; + if (options.projectId) { + reqOpts.projectId = options.projectId; + } + this.request(reqOpts, (err, resp) => { + if (err) { + callback!(err, null, null, resp); + return; + } - let nextQuery: GetDatasetsOptions | null = null; + let nextQuery: GetDatasetsOptions | null = null; - if (resp.nextPageToken) { - nextQuery = Object.assign({}, options, { - pageToken: resp.nextPageToken, - }); - } + if (resp.nextPageToken) { + nextQuery = Object.assign({}, options, { + pageToken: resp.nextPageToken, + }); + } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const datasets = (resp.datasets || []).map( - (dataset: bigquery.IDataset) => { - const ds = this.dataset(dataset.datasetReference!.datasetId!, { - location: dataset.location!, - }); - - ds.metadata = dataset!; - return ds; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const datasets = (resp.datasets || []).map( + (dataset: bigquery.IDataset) => { + const dsOpts: DatasetOptions = { + location: dataset.location!, + }; + if (options.projectId) { + dsOpts.projectId = options.projectId; } - ); + const ds = this.dataset(dataset.datasetReference!.datasetId!, dsOpts); - callback!(null, datasets, nextQuery, resp); - } - ); + ds.metadata = dataset!; + return ds; + } + ); + + callback!(null, datasets, nextQuery, resp); + }); } /** diff --git a/src/dataset.ts b/src/dataset.ts index 92580538..9bc8fd44 100644 --- a/src/dataset.ts +++ b/src/dataset.ts @@ -109,6 +109,7 @@ export type TableCallback = ResourceCallback; * @param {BigQuery} bigQuery {@link BigQuery} instance. * @param {string} id The ID of the Dataset. * @param {object} [options] Dataset options. + * @param {string} [options.projectId] The GCP project ID. * @param {string} [options.location] The geographic location of the dataset. * Defaults to US. * @@ -372,7 +373,12 @@ class Dataset extends ServiceObject { typeof optionsOrCallback === 'function' ? (optionsOrCallback as DatasetCallback) : cb; - options = extend({}, options, {location: this.location}); + if (this.location) { + options = extend({}, options, {location: this.location}); + } + if (this.projectId) { + options = extend({}, options, {projectId: this.projectId}); + } return bigQuery.createDataset(id, options, callback!); }, }); diff --git a/test/bigquery.ts b/test/bigquery.ts index 4a216ea3..8e108c05 100644 --- a/test/bigquery.ts +++ b/test/bigquery.ts @@ -148,6 +148,7 @@ afterEach(() => sandbox.restore()); describe('BigQuery', () => { const JOB_ID = 'JOB_ID'; const PROJECT_ID = 'test-project'; + const ANOTHER_PROJECT_ID = 'another-test-project'; const LOCATION = 'asia-northeast1'; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -1592,6 +1593,30 @@ describe('BigQuery', () => { bq.createDataset(DATASET_ID, assert.ifError); }); + it('should create a dataset on a different project', done => { + bq.makeAuthenticatedRequest = (reqOpts: DecorateRequestOptions) => { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.projectId, ANOTHER_PROJECT_ID); + assert.strictEqual( + reqOpts.uri, + `https://bigquery.googleapis.com/bigquery/v2/projects/${ANOTHER_PROJECT_ID}/datasets` + ); + assert.deepStrictEqual(reqOpts.json.datasetReference, { + datasetId: DATASET_ID, + }); + + done(); + }; + + bq.createDataset( + DATASET_ID, + { + projectId: ANOTHER_PROJECT_ID, + }, + assert.ifError + ); + }); + it('should send the location if available', done => { const bq = new BigQuery({ projectId: PROJECT_ID, @@ -2514,6 +2539,20 @@ describe('BigQuery', () => { done(); }); }); + + it('should fetch datasets from a different project', done => { + const queryObject = {projectId: ANOTHER_PROJECT_ID}; + + bq.makeAuthenticatedRequest = (reqOpts: DecorateRequestOptions) => { + assert.strictEqual( + reqOpts.uri, + `https://bigquery.googleapis.com/bigquery/v2/projects/${ANOTHER_PROJECT_ID}/datasets` + ); + done(); + }; + + bq.getDatasets(queryObject, assert.ifError); + }); }); describe('getJobs', () => { diff --git a/test/dataset.ts b/test/dataset.ts index 4fea70fc..0472f90b 100644 --- a/test/dataset.ts +++ b/test/dataset.ts @@ -203,6 +203,20 @@ describe('BigQuery/Dataset', () => { ds.location = LOCATION; config.createMethod(DATASET_ID, done); }); + + it('should pass the projectId', done => { + bq.createDataset = ( + id: string, + options: DatasetOptions, + callback: Function + ) => { + assert.strictEqual(options.projectId, 'project-id'); + callback(); // the done fn + }; + + ds.projectId = 'project-id'; + config.createMethod(DATASET_ID, done); + }); }); describe('projectId override interceptor', () => {