diff --git a/src/index.ts b/src/index.ts index 257d396d..70f96286 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1022,7 +1022,6 @@ export class BigQuery extends common.Service { if ((!options || !options.query) && !options.pageToken) { throw new Error('A SQL query string is required.'); } - // tslint:disable-next-line no-any const query: any = extend( true, @@ -1463,8 +1462,7 @@ export class BigQuery extends common.Service { return new Job(this, id, options); } - query(query: string, options?: QueryOptions): Promise; - query(query: Query, options?: QueryOptions): Promise; + query(query: string, options?: QueryOptions): Promise; query( query: string, options: QueryOptions, @@ -1571,9 +1569,10 @@ export class BigQuery extends common.Service { optionsOrCallback?: | QueryOptions | SimpleQueryRowsCallback + | RowsCallback | QueryRowsCallback, - cb?: SimpleQueryRowsCallback | QueryRowsCallback - ): void | Promise | Promise { + cb?: SimpleQueryRowsCallback | RowsCallback + ): void | Promise { let options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const callback = @@ -1590,7 +1589,23 @@ export class BigQuery extends common.Service { // 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 as QueryRowsCallback); + + // table#getRows uses listTableData endpoint, which is a faster method + // to read rows of results. + + job!.getQueryResults(options, (err, rows) => { + if (!err) { + if (rows!.length !== 0) { + const datasetId = job!.metadata.configuration.query.destinationTable + .datasetId; + const tableId = job!.metadata.configuration.query.destinationTable + .tableId; + const dataset = this.dataset(datasetId); + const table = dataset.table(tableId); + table.getRows(options, callback as RowsCallback); + } + } + }); }); } diff --git a/test/index.ts b/test/index.ts index ac8f9660..fdb3e332 100644 --- a/test/index.ts +++ b/test/index.ts @@ -29,6 +29,7 @@ import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import * as sinon from 'sinon'; import * as uuid from 'uuid'; +import {EventEmitter} from 'events'; import {BigQueryDate, Dataset, Job, Table} from '../src'; import {JobOptions} from '../src/job'; @@ -66,52 +67,6 @@ const fakeUtil = extend({}, util, { }); const originalFakeUtil = extend(true, {}, fakeUtil); -class FakeDataset { - calledWith_: IArguments; - constructor() { - this.calledWith_ = arguments; - } -} - -class FakeTable extends Table { - constructor(a: Dataset, b: string) { - super(a, b); - } -} - -class FakeJob { - calledWith_: IArguments; - constructor() { - this.calledWith_ = arguments; - } -} - -let extended = false; -const fakePaginator = { - paginator: { - extend: (c: Function, methods: string[]) => { - if (c.name !== 'BigQuery') { - return; - } - methods = arrify(methods); - assert.strictEqual(c.name, 'BigQuery'); - assert.deepStrictEqual(methods, ['getDatasets', 'getJobs']); - extended = true; - }, - streamify: (methodName: string) => { - return methodName; - }, - }, -}; - -class FakeService extends Service { - calledWith_: IArguments; - constructor(config: ServiceConfig, options: ServiceOptions) { - super(config, options); - this.calledWith_ = arguments; - } -} - const sandbox = sinon.createSandbox(); afterEach(() => sandbox.restore()); @@ -127,6 +82,57 @@ describe('BigQuery', () => { // tslint:disable-next-line no-any let bq: any; + class FakeTable extends Table { + constructor(a: Dataset, b: string) { + super(a, b); + } + } + + class FakeDataset extends Dataset { + calledWith_: IArguments; + constructor() { + super(bq, '1'); + this.calledWith_ = arguments; + } + + table(id: string): FakeTable { + return new FakeTable(this, id); + } + } + + class FakeJob { + calledWith_: IArguments; + constructor() { + this.calledWith_ = arguments; + } + } + + let extended = false; + const fakePaginator = { + paginator: { + extend: (c: Function, methods: string[]) => { + if (c.name !== 'BigQuery') { + return; + } + methods = arrify(methods); + assert.strictEqual(c.name, 'BigQuery'); + assert.deepStrictEqual(methods, ['getDatasets', 'getJobs']); + extended = true; + }, + streamify: (methodName: string) => { + return methodName; + }, + }, + }; + + class FakeService extends Service { + calledWith_: IArguments; + constructor(config: ServiceConfig, options: ServiceOptions) { + super(config, options); + this.calledWith_ = arguments; + } + } + before(() => { BigQuery = proxyquire('../src', { uuid: fakeUuid, @@ -1758,21 +1764,19 @@ describe('BigQuery', () => { const FAKE_ROWS = [{}, {}, {}]; const FAKE_RESPONSE = {}; const QUERY_STRING = 'SELECT * FROM [dataset.table]'; - - it('should return any errors from createQueryJob', done => { - const error = new Error('err'); - - bq.createQueryJob = (query: {}, callback: Function) => { - callback(error, null, FAKE_RESPONSE); - }; - - bq.query(QUERY_STRING, (err: Error, rows: {}, resp: {}) => { - assert.strictEqual(err, error); - assert.strictEqual(rows, null); - assert.strictEqual(resp, FAKE_RESPONSE); - done(); - }); - }); + const MODEL_QUERY_STRING = `CREATE OR REPLACE MODEL \`dataset.model\``; + const TABLE_ID = 'bq_table'; + const DATASET_ID = 'bq_dataset'; + const METADATA = { + configuration: { + query: { + destinationTable: { + datasetId: DATASET_ID, + tableId: TABLE_ID, + }, + }, + }, + }; it('should exit early if dryRun is set', done => { const options = { @@ -1793,30 +1797,47 @@ describe('BigQuery', () => { }); }); - it('should call job#getQueryResults', done => { + it('should call table#getRows', done => { + // const fakeJob = new EventEmitter(); const fakeJob = { getQueryResults: (options: {}, callback: Function) => { callback(null, FAKE_ROWS, FAKE_RESPONSE); }, }; + // tslint:disable-next-line: no-any + (fakeJob as any).metadata = METADATA; bq.createQueryJob = (query: {}, callback: Function) => { callback(null, fakeJob, FAKE_RESPONSE); }; - bq.query(QUERY_STRING, (err: Error, rows: {}, resp: {}) => { - assert.ifError(err); - assert.strictEqual(rows, FAKE_ROWS); - assert.strictEqual(resp, FAKE_RESPONSE); - done(); - }); + const fakeTable = { + getRows(options: {}, cb: Function) { + assert.deepStrictEqual(options, {job: fakeJob}); + cb(); // done() + }, + }; + + const fakeDataset = { + table(id: string) { + assert.strictEqual(id, TABLE_ID); + return fakeTable; + }, + }; + + bq.dataset = (id: string) => { + assert.strictEqual(id, DATASET_ID); + return fakeDataset; + }; + + bq.query(QUERY_STRING, done); + // fakeJob.emit('complete', METADATA); }); - it('should assign Job on the options', done => { + it('should call job#getQueryResults for model query', done => { const fakeJob = { getQueryResults: (options: {}, callback: Function) => { - assert.deepStrictEqual(options, {job: fakeJob}); - done(); + callback(null, FAKE_ROWS, FAKE_RESPONSE); }, }; @@ -1824,28 +1845,53 @@ describe('BigQuery', () => { callback(null, fakeJob, FAKE_RESPONSE); }; - bq.query(QUERY_STRING, assert.ifError); + bq.query(MODEL_QUERY_STRING, (err: Error, rows: {}, resp: {}) => { + assert.ifError(err); + assert.strictEqual(rows, FAKE_ROWS); + assert.strictEqual(resp, FAKE_RESPONSE); + done(); + }); }); - it('should optionally accept options', done => { - const fakeOptions = {}; + it('should return any errors from createQueryJob', done => { + const error = new Error('err'); + + bq.createQueryJob = (query: {}, callback: Function) => { + callback(error, null, FAKE_RESPONSE); + }; + + bq.query(QUERY_STRING, (err: Error, rows: {}, resp: {}) => { + assert.strictEqual(err, error); + assert.strictEqual(rows, null); + assert.strictEqual(resp, FAKE_RESPONSE); + done(); + }); + }); + + it('should return any errors from job', done => { const fakeJob = { - getQueryResults: (options: {}) => { - assert.notStrictEqual(options, fakeOptions); - assert.deepStrictEqual(options, {job: fakeJob}); - done(); + getQueryResults: (options: {}, callback: Function) => { + callback(null, FAKE_ROWS, FAKE_RESPONSE); }, }; - bq.createQueryJob = (query: {}, callback: Function) => { - callback(null, fakeJob, FAKE_RESPONSE); + const error = new Error('Error.'); + + bq.createQueryJob = ({}, callback: Function) => { + callback(null, fakeJob, error); }; - bq.query(QUERY_STRING, fakeOptions, assert.ifError); + bq.query(QUERY_STRING, (err: Error) => { + assert.strictEqual(err, error); + done(); + }); }); }); describe('queryAsStream_', () => { + const FAKE_ROWS = [{}, {}, {}]; + const FAKE_RESPONSE = {}; + it('should call query correctly', done => { const query = 'SELECT'; bq.query = (query_: {}, options: {}, callback: Function) => { @@ -1855,5 +1901,22 @@ describe('BigQuery', () => { }; bq.queryAsStream_(query, done); }); + + it('should call query correctly with a job', done => { + const fakeJob = { + getQueryResults: (query: {}, callback: Function) => { + assert.strictEqual(query, query); + callback(null, FAKE_ROWS, FAKE_RESPONSE); + }, + }; + + const query = {job: fakeJob}; + + bq.query = (query_: {}, options: {}, callback: Function) => { + assert.strictEqual(query_, query); + callback(); // done() + }; + bq.queryAsStream_(query, done); + }); }); });