Skip to content

Commit e500d40

Browse files
feat(bigquery): allow the user to ask for skipping parsing rows when querying (#7848)
1 parent 1b44bd1 commit e500d40

8 files changed

Lines changed: 258 additions & 21 deletions

File tree

handwritten/bigquery/src/bigquery.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export type Query = JobRequest<bigquery.IJobConfigurationQuery> & {
128128
pageToken?: string;
129129
wrapIntegers?: boolean | IntegerTypeCastOptions;
130130
parseJSON?: boolean;
131+
skipParsing?: boolean;
131132
// Overrides default job creation mode set on the client.
132133
jobCreationMode?: JobCreationMode;
133134
};
@@ -2217,6 +2218,7 @@ export class BigQuery extends Service {
22172218
? {
22182219
wrapIntegers: query.wrapIntegers,
22192220
parseJSON: query.parseJSON,
2221+
skipParsing: query.skipParsing,
22202222
}
22212223
: {};
22222224
const callback =
@@ -2268,12 +2270,16 @@ export class BigQuery extends Service {
22682270
'formatOptions.useInt64Timestamp':
22692271
queryReq.formatOptions?.useInt64Timestamp,
22702272
};
2271-
rows = BigQuery.mergeSchemaWithRows_(res.schema, res.rows, {
2272-
wrapIntegers: options.wrapIntegers || false,
2273-
parseJSON: options.parseJSON,
2274-
listParams,
2275-
});
2276-
delete res.rows;
2273+
if (options.skipParsing) {
2274+
rows = res.rows;
2275+
} else {
2276+
rows = BigQuery.mergeSchemaWithRows_(res.schema, res.rows, {
2277+
wrapIntegers: options.wrapIntegers || false,
2278+
parseJSON: options.parseJSON,
2279+
listParams,
2280+
});
2281+
delete res.rows;
2282+
}
22772283
} catch (e) {
22782284
(callback as SimpleQueryRowsCallback)(e as Error, null, job);
22792285
return;

handwritten/bigquery/src/job.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type QueryResultsOptions = {
5151
job?: Job;
5252
wrapIntegers?: boolean | IntegerTypeCastOptions;
5353
parseJSON?: boolean;
54+
skipParsing?: boolean;
5455
} & PagedRequest<bigquery.jobs.IGetQueryResultsParams> & {
5556
/**
5657
* internal properties
@@ -602,10 +603,15 @@ class Job extends Operation {
602603
error never makes it to the callback. Instead, pass the error to the
603604
callback the user provides so that the user can see the error.
604605
*/
605-
rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
606-
wrapIntegers,
607-
parseJSON,
608-
});
606+
if (options.skipParsing) {
607+
rows = resp.rows;
608+
} else {
609+
rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
610+
wrapIntegers,
611+
parseJSON,
612+
});
613+
delete resp.rows;
614+
}
609615
} catch (e) {
610616
callback!(e as Error, null, null, resp);
611617
return;
@@ -633,7 +639,6 @@ class Job extends Operation {
633639
});
634640
delete nextQuery.startIndex;
635641
}
636-
delete resp.rows;
637642
callback!(null, rows, nextQuery, resp);
638643
},
639644
);

handwritten/bigquery/src/table.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export type TableRowValue = string | TableRow;
114114
export type GetRowsOptions = PagedRequest<bigquery.tabledata.IListParams> & {
115115
wrapIntegers?: boolean | IntegerTypeCastOptions;
116116
parseJSON?: boolean;
117+
skipParsing?: boolean;
117118
};
118119

119120
export type JobLoadMetadata = JobRequest<bigquery.IJobConfigurationLoad> & {
@@ -1873,12 +1874,20 @@ class Table extends ServiceObject {
18731874
the callback. Instead, pass the error to the callback the user provides
18741875
so that the user can see the error.
18751876
*/
1876-
rows = BigQuery.mergeSchemaWithRows_(this.metadata.schema, rows || [], {
1877-
wrapIntegers,
1878-
selectedFields,
1879-
parseJSON,
1880-
listParams: qs,
1881-
});
1877+
if (options.skipParsing) {
1878+
rows = rows || [];
1879+
} else {
1880+
rows = BigQuery.mergeSchemaWithRows_(
1881+
this.metadata.schema,
1882+
rows || [],
1883+
{
1884+
wrapIntegers,
1885+
selectedFields,
1886+
parseJSON,
1887+
listParams: qs,
1888+
},
1889+
);
1890+
}
18821891
} catch (err) {
18831892
callback!(err as Error | null, null, null, resp);
18841893
return;

handwritten/bigquery/system-test/bigquery.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,27 @@ describe('BigQuery', () => {
354354
assert.strictEqual(res.totalRows, '100');
355355
});
356356

357+
it('should query with skipParsing', async () => {
358+
const [rows] = await bigquery.query({
359+
query,
360+
skipParsing: true,
361+
});
362+
assert.strictEqual(rows.length, 100);
363+
// Raw rows have an 'f' property containing the fields
364+
assert.ok(rows[0].f);
365+
assert.strictEqual(typeof rows[0].f[0].v, 'string');
366+
});
367+
368+
it('should query with skipParsing via Dataset.query', async () => {
369+
const [rows] = await dataset.query({
370+
query,
371+
skipParsing: true,
372+
});
373+
assert.strictEqual(rows.length, 100);
374+
assert.ok(rows[0].f);
375+
assert.strictEqual(typeof rows[0].f[0].v, 'string');
376+
});
377+
357378
it('should query without jobs.query and return all PagedResponse as positional parameters', async () => {
358379
// force jobs.getQueryResult instead of fast query path
359380
const jobId = generateName('job');
@@ -575,8 +596,9 @@ describe('BigQuery', () => {
575596
const QUERY = `SELECT * FROM \`${table.id}\``;
576597
// eslint-disable-next-line @typescript-eslint/no-var-requires
577598
const SCHEMA = require('../../system-test/data/schema.json');
578-
const TEST_DATA_FILE =
579-
require.resolve('../../system-test/data/location-test-data.json');
599+
const TEST_DATA_FILE = require.resolve(
600+
'../../system-test/data/location-test-data.json',
601+
);
580602

581603
before(async () => {
582604
// create a dataset in a certain location will cascade the location
@@ -879,8 +901,9 @@ describe('BigQuery', () => {
879901
});
880902

881903
describe('BigQuery/Table', () => {
882-
const TEST_DATA_JSON_PATH =
883-
require.resolve('../../system-test/data/kitten-test-data.json');
904+
const TEST_DATA_JSON_PATH = require.resolve(
905+
'../../system-test/data/kitten-test-data.json',
906+
);
884907

885908
it('should have created the correct schema', () => {
886909
assert.deepStrictEqual(table.metadata.schema.fields, SCHEMA);

handwritten/bigquery/test/bigquery.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3243,6 +3243,74 @@ describe('BigQuery', () => {
32433243
});
32443244
});
32453245

3246+
it('should delete res.rows if skipParsing is false', done => {
3247+
const rawRows = [{f: [{v: 'hi'}]}];
3248+
const resp = {
3249+
jobComplete: true,
3250+
schema: {
3251+
fields: [{name: 'name', type: 'STRING'}],
3252+
},
3253+
rows: rawRows,
3254+
};
3255+
3256+
const job = {
3257+
getQueryResults: (options: {}, callback: Function) => {
3258+
callback(null, [], null, resp);
3259+
},
3260+
};
3261+
3262+
bq.runJobsQuery = (reqOpts: {}, callback: Function) => {
3263+
callback(null, job, resp);
3264+
};
3265+
3266+
bq.query(
3267+
{
3268+
query: 'SELECT * FROM table',
3269+
skipParsing: false,
3270+
},
3271+
(err: Error, rows: {}[], nextQuery: {}, response: any) => {
3272+
assert.ifError(err);
3273+
// the job Complete callback returned the resp
3274+
assert.deepStrictEqual(response.rows, undefined);
3275+
done();
3276+
},
3277+
);
3278+
});
3279+
3280+
it('should skip parsing if skipParsing is true', done => {
3281+
const rawRows = [{f: [{v: 'hi'}]}];
3282+
const resp = {
3283+
jobComplete: true,
3284+
schema: {
3285+
fields: [{name: 'name', type: 'STRING'}],
3286+
},
3287+
rows: rawRows,
3288+
};
3289+
3290+
const job = {
3291+
getQueryResults: (options: QueryResultsOptions, callback: Function) => {
3292+
callback(null, options._cachedRows, null, options._cachedResponse);
3293+
},
3294+
};
3295+
3296+
bq.runJobsQuery = (reqOpts: {}, callback: Function) => {
3297+
callback(null, job, resp);
3298+
};
3299+
3300+
bq.query(
3301+
{
3302+
query: 'SELECT * FROM table',
3303+
skipParsing: true,
3304+
},
3305+
(err: Error, rows: {}[], nextQuery: {}, response: any) => {
3306+
assert.ifError(err);
3307+
assert.strictEqual(rows, rawRows);
3308+
assert.deepStrictEqual(response.rows, rawRows);
3309+
done();
3310+
},
3311+
);
3312+
});
3313+
32463314
it('should call job#getQueryResults with query options', done => {
32473315
let queryResultsOpts = {};
32483316
const fakeJob = {

handwritten/bigquery/test/dataset.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,20 @@ describe('BigQuery/Dataset', () => {
10101010
ds.query(query);
10111011
});
10121012

1013+
it('should pass along skipParsing', done => {
1014+
const query = {
1015+
query: 'SELECT * FROM allthedata',
1016+
skipParsing: true,
1017+
};
1018+
1019+
ds.bigQuery.query = (opts: _root.Query) => {
1020+
assert.strictEqual(opts.skipParsing, true);
1021+
done();
1022+
};
1023+
1024+
ds.query(query);
1025+
});
1026+
10131027
it('should pass along options', done => {
10141028
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10151029
ds.bigQuery.query = (opts: any) => {

handwritten/bigquery/test/job.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,57 @@ describe('BigQuery/Job', () => {
403403
job.getQueryResults(options, assert.ifError);
404404
});
405405

406+
it('should skip parsing if skipParsing is true', done => {
407+
const response = {
408+
schema: {},
409+
rows: [{f: [{v: 'hi'}]}],
410+
};
411+
412+
BIGQUERY.request = (
413+
reqOpts: DecorateRequestOptions,
414+
callback: Function,
415+
) => {
416+
callback(null, response);
417+
};
418+
419+
const mergeStub = sandbox.stub(BigQuery, 'mergeSchemaWithRows_');
420+
421+
job.getQueryResults({skipParsing: true}, (err: Error, rows: {}[]) => {
422+
assert.ifError(err);
423+
assert.strictEqual(rows, response.rows);
424+
assert.strictEqual(mergeStub.called, false);
425+
done();
426+
});
427+
});
428+
429+
it('should not delete resp.rows if skipParsing is true', done => {
430+
const options: QueryResultsOptions = {
431+
skipParsing: true,
432+
};
433+
434+
const rawRows = [{f: [{v: 'hi'}]}];
435+
const resp = {
436+
jobComplete: true,
437+
rows: rawRows,
438+
schema: {
439+
fields: [{name: 'name', type: 'STRING'}],
440+
},
441+
};
442+
443+
job.bigQuery.request = (reqOpts: {}, callback: Function) => {
444+
callback(null, resp);
445+
};
446+
447+
job.getQueryResults(
448+
options,
449+
(err: Error, rows: {}, nextQuery: {}, response: any) => {
450+
assert.ifError(err);
451+
assert.deepStrictEqual(response.rows, rawRows);
452+
done();
453+
},
454+
);
455+
});
456+
406457
it('should return the query when the job is not complete', done => {
407458
BIGQUERY.request = (
408459
reqOpts: DecorateRequestOptions,
@@ -447,6 +498,32 @@ describe('BigQuery/Job', () => {
447498
);
448499
});
449500

501+
it('should delete resp.rows if skipParsing is false by default', done => {
502+
const options: QueryResultsOptions = {};
503+
504+
const rawRows = [{f: [{v: 'hi'}]}];
505+
const resp = {
506+
jobComplete: true,
507+
rows: rawRows,
508+
schema: {
509+
fields: [{name: 'name', type: 'STRING'}],
510+
},
511+
};
512+
513+
job.bigQuery.request = (reqOpts: {}, callback: Function) => {
514+
callback(null, resp);
515+
};
516+
517+
job.getQueryResults(
518+
options,
519+
(err: Error, rows: {}, nextQuery: {}, response: any) => {
520+
assert.ifError(err);
521+
assert.deepStrictEqual(response.rows, undefined);
522+
done();
523+
},
524+
);
525+
});
526+
450527
it('should populate nextQuery when more results exist', done => {
451528
job.getQueryResults(
452529
options,

handwritten/bigquery/test/table.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,6 +2189,27 @@ describe('BigQuery/Table', () => {
21892189
});
21902190
});
21912191

2192+
it('should skip parsing if skipParsing is true', done => {
2193+
const rows = [{f: [{v: 'stephen'}]}];
2194+
const schema = {fields: [{name: 'name', type: 'string'}]};
2195+
table.metadata = {schema};
2196+
2197+
table.request = (reqOpts: DecorateRequestOptions, callback: Function) => {
2198+
callback(null, {rows});
2199+
};
2200+
2201+
sandbox.restore();
2202+
const mergeStub = sandbox.stub(BigQuery, 'mergeSchemaWithRows_');
2203+
2204+
table.getRows({skipParsing: true}, (err: Error, rows_: {}[], nextQuery: {}, apiResponse: any) => {
2205+
assert.ifError(err);
2206+
assert.strictEqual(rows_, rows);
2207+
assert.strictEqual(mergeStub.called, false);
2208+
assert.deepStrictEqual(apiResponse.rows, rows);
2209+
done();
2210+
});
2211+
});
2212+
21922213
it('should pass nextQuery if pageToken is returned', done => {
21932214
const options = {a: 'b', c: 'd'};
21942215
const pageToken = 'token';
@@ -3025,6 +3046,20 @@ describe('BigQuery/Table', () => {
30253046

30263047
table.query('a', 'b');
30273048
});
3049+
3050+
it('should pass skipParsing through to datasetInstance.query()', done => {
3051+
const query = {
3052+
query: 'a',
3053+
skipParsing: true,
3054+
};
3055+
table.dataset.query = (a: {}, b: {}) => {
3056+
assert.deepStrictEqual(a, query);
3057+
assert.strictEqual(b, 'b');
3058+
done();
3059+
};
3060+
3061+
table.query(query, 'b');
3062+
});
30283063
});
30293064

30303065
describe('setMetadata', () => {

0 commit comments

Comments
 (0)