Skip to content
This repository was archived by the owner on Feb 7, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
35b951b
Pull changes from other branch
danieljbruce Jan 28, 2026
8bcc181
Support format options for building queries
danieljbruce Jan 28, 2026
6842256
feat: add parameterized system tests for high-precision query options
google-labs-jules[bot] Jan 28, 2026
74a6639
better test parameterization
danieljbruce Jan 29, 2026
e556aa0
expected ts value should be a timestamp
danieljbruce Jan 29, 2026
12be863
only
danieljbruce Jan 29, 2026
f6fe6fb
generate the new file
danieljbruce Jan 29, 2026
0dac60d
Modify tests to emulate bigquery and call
danieljbruce Jan 29, 2026
211fd14
uncomment the rest of the tests
danieljbruce Jan 29, 2026
ff7fdec
now all tests pass
danieljbruce Jan 29, 2026
f3b509c
return full string
danieljbruce Jan 29, 2026
42c113a
get passing tests
danieljbruce Jan 29, 2026
72c7680
Correct a description
danieljbruce Jan 29, 2026
711a0b2
Merge branch 'main' of https://github.com/googleapis/nodejs-bigquery …
danieljbruce Jan 30, 2026
00bccf8
Add try/catch blocks
danieljbruce Jan 30, 2026
ffc419d
Set describe blocks to only
danieljbruce Jan 30, 2026
6bec4e6
reorder tests to match timestamp_output_format tes
danieljbruce Feb 2, 2026
6cedd39
Set a default format options matching getRows call
danieljbruce Feb 2, 2026
661cc9b
Clean up tests and expected error message
danieljbruce Feb 2, 2026
a0f3dee
check right error message
danieljbruce Feb 2, 2026
c8c046c
Correct format options parameter passing
danieljbruce Feb 2, 2026
c893cff
Change listParams shape
danieljbruce Feb 2, 2026
dc37d26
eliminate timestamp precision 12
danieljbruce Feb 2, 2026
2984d0b
don’t change main system tests file
danieljbruce Feb 2, 2026
7959fa8
Run the linter
danieljbruce Feb 2, 2026
bcfdc40
Change the header year
danieljbruce Feb 2, 2026
5e72c89
delete the server results code
danieljbruce Feb 2, 2026
f150fe9
remove the isAuthError variable
danieljbruce Feb 2, 2026
2e44249
update the unit tests
danieljbruce Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions src/bigquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,11 @@ export class BigQuery extends Service {
};
}),
};
} else if ((providedType as string).toUpperCase() === 'TIMESTAMP') {
return {
type: 'TIMESTAMP',
timestampPrecision: '12',
};
}

providedType = (providedType as string).toUpperCase();
Expand Down Expand Up @@ -1137,7 +1142,10 @@ export class BigQuery extends Service {
} else if (value instanceof BigQueryTime) {
typeName = 'TIME';
} else if (value instanceof BigQueryTimestamp) {
typeName = 'TIMESTAMP';
return {
type: 'TIMESTAMP', // Was typeName = 'TIMESTAMP', but now we need better timestamp precision;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if the comment here is needed

timestampPrecision: '12',
};
} else if (value instanceof Buffer) {
typeName = 'BYTES';
} else if (value instanceof Big) {
Expand Down Expand Up @@ -2249,11 +2257,30 @@ export class BigQuery extends Service {
if (res && res.jobComplete) {
let rows: any = [];
if (res.schema && res.rows) {
rows = BigQuery.mergeSchemaWithRows_(res.schema, res.rows, {
wrapIntegers: options.wrapIntegers || false,
parseJSON: options.parseJSON,
});
delete res.rows;
try {
/*
Without this try/catch block, calls to getRows will hang indefinitely if

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRows = jobsQuery

a call to mergeSchemaWithRows_ fails because the error never makes it to
the callback. Instead, pass the error to the callback the user provides
so that the user can see the error.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const listParams = {
'formatOptions.timestampOutputFormat':
queryReq.formatOptions?.timestampOutputFormat,
'formatOptions.useInt64Timestamp':
queryReq.formatOptions?.useInt64Timestamp,
};
rows = BigQuery.mergeSchemaWithRows_(res.schema, res.rows, {
wrapIntegers: options.wrapIntegers || false,
parseJSON: options.parseJSON,
listParams,
});
delete res.rows;
} catch (e) {
(callback as SimpleQueryRowsCallback)(e as Error, null, job);
return;
}
}
this.trace_('[runJobsQuery] job complete');
options._cachedRows = rows;
Expand Down Expand Up @@ -2334,6 +2361,18 @@ export class BigQuery extends Service {
if (options.job) {
return undefined;
}
const hasAnyFormatOpts =
options['formatOptions.timestampOutputFormat'] !== undefined ||
options['formatOptions.useInt64Timestamp'] !== undefined;
const defaultOpts = hasAnyFormatOpts
? {}
: {
timestampOutputFormat: 'ISO8601_STRING',
};
const formatOptions = extend(defaultOpts, {
timestampOutputFormat: options['formatOptions.timestampOutputFormat'],
useInt64Timestamp: options['formatOptions.useInt64Timestamp'],
});
const req: bigquery.IQueryRequest = {
useQueryCache: queryObj.useQueryCache,
labels: queryObj.labels,
Expand All @@ -2342,9 +2381,7 @@ export class BigQuery extends Service {
maximumBytesBilled: queryObj.maximumBytesBilled,
timeoutMs: options.timeoutMs,
location: queryObj.location || options.location,
formatOptions: {
useInt64Timestamp: true,
},
formatOptions,
maxResults: queryObj.maxResults || options.maxResults,
query: queryObj.query,
useLegacySql: false,
Expand Down
19 changes: 15 additions & 4 deletions src/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,10 +595,21 @@ class Job extends Operation {
let rows: any = [];

if (resp.schema && resp.rows) {
rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
wrapIntegers,
parseJSON,
});
try {
/*
Without this try/catch block, calls to getRows will hang indefinitely if

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRows = jobsQuery endpoint ?

a call to mergeSchemaWithRows_ fails because the error never makes it to
the callback. Instead, pass the error to the callback the user provides
so that the user can see the error.
*/
rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, {
wrapIntegers,
parseJSON,
});
} catch (e) {
callback!(e as Error, null, null, resp);
return;
}
}

let nextQuery: QueryResultsOptions | null = null;
Expand Down
165 changes: 165 additions & 0 deletions system-test/high-precision-query-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2026 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.

import * as assert from 'assert';
import {describe, it, before} from 'mocha';
import {BigQuery} from '../src';

describe.only('High Precision Query System Tests', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a separated file ? The previous PR already deviated from the pattern of having system tests in the https://github.com/googleapis/nodejs-bigquery/blob/main/system-test/bigquery.ts and added https://github.com/googleapis/nodejs-bigquery/blob/main/system-test/timestamp_output_format.ts, can we at least bundle all timestamp related tests into the same file ?

And I'm going to bring again that we don't need the amount of combinations and system tests to check if the support for picosecond resolution is working. More tests doesn't necessarily means more coverage. Just with those two PR we are adding 28 query executions, where previously we had a total of around 50. Not saying that the coverage was perfect before, but adding too many system/integration test just for a time format change. It makes CI slower, and I'm not seeing enough justification for the amount of combination and tests being added here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already got bitten by too much client side validation or tests trying to assert internal details of the service in the past that can cause problems. In this case, is not a SDK code, but is test that can fail in the future if things changes in the service.

See internal b/460198628

let bigquery: BigQuery;
const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was suppose to be Nanosecond

const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456789123Z';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const expectedErrorMessage =
'Cannot specify both timestamp_as_int and timestamp_output_format.';

before(() => {
bigquery = new BigQuery();
});

const testCases = [
{
name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: true',
timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED',
useInt64Timestamp: true,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)',
timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED',
useInt64Timestamp: false,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: FLOAT64, UI64: true (error)',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the service side is going to reject it, we don't need to assert this. If the service side changes behavior (and can happen) we are gonna have a flaky test.

timestampOutputFormat: 'FLOAT64',
useInt64Timestamp: true,
expectedTsValue: undefined,
expectedError: expectedErrorMessage,
},
{
name: 'TOF: FLOAT64, UI64: false',
timestampOutputFormat: 'FLOAT64',
useInt64Timestamp: false,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: INT64, UI64: true',
timestampOutputFormat: 'INT64',
useInt64Timestamp: true,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: INT64, UI64: false (error)',
timestampOutputFormat: 'INT64',
useInt64Timestamp: false,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: ISO8601_STRING, UI64: true (error)',
timestampOutputFormat: 'ISO8601_STRING',
useInt64Timestamp: true,
expectedTsValue: undefined,
expectedError: expectedErrorMessage,
},
{
name: 'TOF: ISO8601_STRING, UI64: false',
timestampOutputFormat: 'ISO8601_STRING',
useInt64Timestamp: false,
expectedTsValue: expectedTsValueNanoseconds,
},
{
name: 'TOF: omitted, UI64: omitted (default INT64)',
timestampOutputFormat: undefined,
useInt64Timestamp: undefined,
expectedTsValue: expectedTsValueNanoseconds,
},
{
name: 'TOF: omitted, UI64: true',
timestampOutputFormat: undefined,
useInt64Timestamp: true,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: omitted, UI64: false (default ISO8601_STRING)',
timestampOutputFormat: undefined,
useInt64Timestamp: false,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)',
timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED',
useInt64Timestamp: undefined,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: FLOAT64, UI64: omitted (error)',
timestampOutputFormat: 'FLOAT64',
useInt64Timestamp: undefined,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: INT64, UI64: omitted',
timestampOutputFormat: 'INT64',
useInt64Timestamp: undefined,
expectedTsValue: expectedTsValueMicroseconds,
},
{
name: 'TOF: ISO8601_STRING, UI64: omitted (error)',
timestampOutputFormat: 'ISO8601_STRING',
useInt64Timestamp: undefined,
expectedTsValue: expectedTsValueNanoseconds,
},
];

testCases.forEach(testCase => {
it(`should handle ${testCase.name}`, async function () {
const query = {
query: 'SELECT ? as ts',
params: [bigquery.timestamp('2023-01-01T12:00:00.123456789123Z')],
};

const options: any = {};
if (testCase.timestampOutputFormat !== undefined) {
options['formatOptions.timestampOutputFormat'] =
testCase.timestampOutputFormat;
}
if (testCase.useInt64Timestamp !== undefined) {
options['formatOptions.useInt64Timestamp'] = testCase.useInt64Timestamp;
}

try {
const [rows] = await bigquery.query(query, options);
if (testCase.expectedError) {
assert.fail(
`Query should have failed for ${testCase.name}, but succeeded`,
);
}
assert.ok(rows.length > 0);
assert.ok(rows[0].ts.value !== undefined);
assert.strictEqual(rows[0].ts.value, testCase.expectedTsValue);
} catch (err: any) {
if (!testCase.expectedError) {
throw err;
}

const message = err.message;
assert.strictEqual(
message,
testCase.expectedError,
`Expected ${testCase.expectedError} error for ${testCase.name}, got ${message} (${err.message})`,
);
}
});
});
});
4 changes: 2 additions & 2 deletions test/bigquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3397,7 +3397,7 @@ describe('BigQuery', () => {
},
jobCreationMode: 'JOB_CREATION_REQUIRED',
formatOptions: {
useInt64Timestamp: true,
timestampOutputFormat: 'ISO8601_STRING',
},
};
assert.deepStrictEqual(req, expectedReq);
Expand All @@ -3416,7 +3416,7 @@ describe('BigQuery', () => {
requestId: req.requestId,
jobCreationMode: 'JOB_CREATION_OPTIONAL',
formatOptions: {
useInt64Timestamp: true,
timestampOutputFormat: 'ISO8601_STRING',
},
};
assert.deepStrictEqual(req, expectedReq);
Expand Down
Loading