From aad102a98e985917826817f6fed4e225711bfced Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 5 Feb 2026 11:09:01 -0500 Subject: [PATCH 01/14] move over all code changes supporting high precision timestamps --- handwritten/bigquery/src/bigquery.ts | 154 ++++++++---- handwritten/bigquery/src/job.ts | 19 +- handwritten/bigquery/system-test/bigquery.ts | 15 +- .../system-test/high-precision-query-tests.ts | 234 ++++++++++++++++++ handwritten/bigquery/test/bigquery.ts | 4 +- 5 files changed, 373 insertions(+), 53 deletions(-) create mode 100644 handwritten/bigquery/system-test/high-precision-query-tests.ts diff --git a/handwritten/bigquery/src/bigquery.ts b/handwritten/bigquery/src/bigquery.ts index 3057394364a..89596f050b4 100644 --- a/handwritten/bigquery/src/bigquery.ts +++ b/handwritten/bigquery/src/bigquery.ts @@ -1099,6 +1099,11 @@ export class BigQuery extends Service { }; }), }; + } else if ((providedType as string).toUpperCase() === 'TIMESTAMP(12)') { + return { + type: 'TIMESTAMP', + timestampPrecision: '12', + }; } providedType = (providedType as string).toUpperCase(); @@ -2249,11 +2254,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 + 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; @@ -2334,6 +2358,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, @@ -2342,9 +2378,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, @@ -2466,6 +2500,59 @@ promisifyAll(BigQuery, { ], }); +function fromStringValue_(value: string): [start: string, end: string] { + /* + This method returns start and end values for RANGE typed values returned from + the server. It decodes the server RANGE value into start and end values so + they can be used to construct a BigQueryRange. + */ + let cleanedValue = value; + if (cleanedValue.startsWith('[') || cleanedValue.startsWith('(')) { + cleanedValue = cleanedValue.substring(1); + } + if (cleanedValue.endsWith(')') || cleanedValue.endsWith(']')) { + cleanedValue = cleanedValue.substring(0, cleanedValue.length - 1); + } + const parts = cleanedValue.split(','); + if (parts.length !== 2) { + throw new Error( + 'invalid RANGE. See RANGE literal format docs for more information.', + ); + } + + const [start, end] = parts.map((s: string) => s.trim()); + return [start, end]; +} + +function fromSchemaValue_(value: string, elementType: string, listParams?: + | bigquery.tabledata.IListParams + | bigquery.jobs.IGetQueryResultsParams +) { + /* + This method is only used by convertSchemaFieldValue and only when range + values are passed into convertSchemaFieldValue. It produces a value that is + delivered to the user for read calls and it needs to pass along listParams + to ensure TIMESTAMP types are converted properly. + */ + const [start, end] = fromStringValue_(value); + const convertRangeSchemaValue = (value: string) => { + if (value === 'UNBOUNDED' || value === 'NULL') { + return null; + } + return convertSchemaFieldValue({type: elementType}, value, { + wrapIntegers: false, + listParams + }); + }; + return BigQuery.range( + { + start: convertRangeSchemaValue(start), + end: convertRangeSchemaValue(end), + }, + elementType, + ); +} + function convertSchemaFieldValue( schemaField: TableField, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -2585,10 +2672,11 @@ function convertSchemaFieldValue( break; } case 'RANGE': { - value = BigQueryRange.fromSchemaValue_( + value = fromSchemaValue_( value, schemaField.rangeElementType!.type!, - ); + options.listParams // Required to convert TIMESTAMP values + ) break; } default: @@ -2666,41 +2754,23 @@ export class BigQueryRange { } private static fromStringValue_(value: string): [start: string, end: string] { - let cleanedValue = value; - if (cleanedValue.startsWith('[') || cleanedValue.startsWith('(')) { - cleanedValue = cleanedValue.substring(1); - } - if (cleanedValue.endsWith(')') || cleanedValue.endsWith(']')) { - cleanedValue = cleanedValue.substring(0, cleanedValue.length - 1); - } - const parts = cleanedValue.split(','); - if (parts.length !== 2) { - throw new Error( - 'invalid RANGE. See RANGE literal format docs for more information.', - ); - } - - const [start, end] = parts.map((s: string) => s.trim()); - return [start, end]; + /* + We moved the internals of fromSchemaValue_ to a method outside of this + class, but fromSchemaValue_ calls fromStringValue_ and this fromStringValue_ + method is private so we needed a fromStringValue_ method outside of this + class so it could be accessed by fromSchemaValue_ function. + */ + return fromStringValue_(value); } static fromSchemaValue_(value: string, elementType: string): BigQueryRange { - const [start, end] = BigQueryRange.fromStringValue_(value); - const convertRangeSchemaValue = (value: string) => { - if (value === 'UNBOUNDED' || value === 'NULL') { - return null; - } - return convertSchemaFieldValue({type: elementType}, value, { - wrapIntegers: false, - }); - }; - return BigQuery.range( - { - start: convertRangeSchemaValue(start), - end: convertRangeSchemaValue(end), - }, - elementType, - ); + /* + fromSchemaValue_ functionality needs to pass listParams into + convertSchemaFieldValue. But we didn't want to add another parameter to this + function because it changes the API so the internals of this method were + moved to fromSchemaValue_ which now accepts an extra parameter. + */ + return fromSchemaValue_(value, elementType); } private convertElement_( diff --git a/handwritten/bigquery/src/job.ts b/handwritten/bigquery/src/job.ts index d39f950b7b7..b91839aa1fa 100644 --- a/handwritten/bigquery/src/job.ts +++ b/handwritten/bigquery/src/job.ts @@ -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 + 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; diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 5bbb6f4483a..43143b264cd 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1472,11 +1472,16 @@ describe('BigQuery', () => { ], }, (err, rows) => { - assert.ifError(err); - assert.strictEqual(rows!.length, 1); - done(); - }, - ); + try { + // Without this try block the test runner silently fails + assert.ifError(err); + assert.strictEqual(rows!.length, 1); + done(); + } catch (e) { + done(e); + } + }, + ) }); it('should work with multiple types', done => { diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts new file mode 100644 index 00000000000..e6f87f5d13f --- /dev/null +++ b/handwritten/bigquery/system-test/high-precision-query-tests.ts @@ -0,0 +1,234 @@ +// 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('High Precision Query System Tests', () => { + let bigquery: BigQuery; + const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456789123Z'; + 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)', + 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 () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. Queries using TIMESTAMP_ADD are another example. + */ + const query = { + query: 'SELECT ? as ts', + params: [bigquery.timestamp('2023-01-01T12:00:00.123456789123Z')], + types: ['TIMESTAMP(12)'], + }; + + 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})`, + ); + } + }); + it(`should handle nested ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. + */ + const query = { + query: 'SELECT ? obj', + params: [ + { + nested: { + a: bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), + }, + }, + ], + types: [ + { + nested: { + a: 'TIMESTAMP(12)', + }, + }, + ], + }; + + 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].obj.nested.a.value !== undefined); + assert.strictEqual( + rows[0].obj.nested.a.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})`, + ); + } + }); + }); +}); diff --git a/handwritten/bigquery/test/bigquery.ts b/handwritten/bigquery/test/bigquery.ts index b53a2b8942b..d6d3dbcbbea 100644 --- a/handwritten/bigquery/test/bigquery.ts +++ b/handwritten/bigquery/test/bigquery.ts @@ -3397,7 +3397,7 @@ describe('BigQuery', () => { }, jobCreationMode: 'JOB_CREATION_REQUIRED', formatOptions: { - useInt64Timestamp: true, + timestampOutputFormat: 'ISO8601_STRING', }, }; assert.deepStrictEqual(req, expectedReq); @@ -3416,7 +3416,7 @@ describe('BigQuery', () => { requestId: req.requestId, jobCreationMode: 'JOB_CREATION_OPTIONAL', formatOptions: { - useInt64Timestamp: true, + timestampOutputFormat: 'ISO8601_STRING', }, }; assert.deepStrictEqual(req, expectedReq); From d4b2b0b2cefcacfc34c08e6fe75f7c5a2cfa183f Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 5 Feb 2026 11:09:01 -0500 Subject: [PATCH 02/14] feat: Add high precision TIMESTAMP values for queries --- handwritten/bigquery/src/bigquery.ts | 154 ++++++++---- handwritten/bigquery/src/job.ts | 19 +- handwritten/bigquery/system-test/bigquery.ts | 15 +- .../system-test/high-precision-query-tests.ts | 234 ++++++++++++++++++ handwritten/bigquery/test/bigquery.ts | 4 +- 5 files changed, 373 insertions(+), 53 deletions(-) create mode 100644 handwritten/bigquery/system-test/high-precision-query-tests.ts diff --git a/handwritten/bigquery/src/bigquery.ts b/handwritten/bigquery/src/bigquery.ts index 3057394364a..89596f050b4 100644 --- a/handwritten/bigquery/src/bigquery.ts +++ b/handwritten/bigquery/src/bigquery.ts @@ -1099,6 +1099,11 @@ export class BigQuery extends Service { }; }), }; + } else if ((providedType as string).toUpperCase() === 'TIMESTAMP(12)') { + return { + type: 'TIMESTAMP', + timestampPrecision: '12', + }; } providedType = (providedType as string).toUpperCase(); @@ -2249,11 +2254,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 + 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; @@ -2334,6 +2358,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, @@ -2342,9 +2378,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, @@ -2466,6 +2500,59 @@ promisifyAll(BigQuery, { ], }); +function fromStringValue_(value: string): [start: string, end: string] { + /* + This method returns start and end values for RANGE typed values returned from + the server. It decodes the server RANGE value into start and end values so + they can be used to construct a BigQueryRange. + */ + let cleanedValue = value; + if (cleanedValue.startsWith('[') || cleanedValue.startsWith('(')) { + cleanedValue = cleanedValue.substring(1); + } + if (cleanedValue.endsWith(')') || cleanedValue.endsWith(']')) { + cleanedValue = cleanedValue.substring(0, cleanedValue.length - 1); + } + const parts = cleanedValue.split(','); + if (parts.length !== 2) { + throw new Error( + 'invalid RANGE. See RANGE literal format docs for more information.', + ); + } + + const [start, end] = parts.map((s: string) => s.trim()); + return [start, end]; +} + +function fromSchemaValue_(value: string, elementType: string, listParams?: + | bigquery.tabledata.IListParams + | bigquery.jobs.IGetQueryResultsParams +) { + /* + This method is only used by convertSchemaFieldValue and only when range + values are passed into convertSchemaFieldValue. It produces a value that is + delivered to the user for read calls and it needs to pass along listParams + to ensure TIMESTAMP types are converted properly. + */ + const [start, end] = fromStringValue_(value); + const convertRangeSchemaValue = (value: string) => { + if (value === 'UNBOUNDED' || value === 'NULL') { + return null; + } + return convertSchemaFieldValue({type: elementType}, value, { + wrapIntegers: false, + listParams + }); + }; + return BigQuery.range( + { + start: convertRangeSchemaValue(start), + end: convertRangeSchemaValue(end), + }, + elementType, + ); +} + function convertSchemaFieldValue( schemaField: TableField, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -2585,10 +2672,11 @@ function convertSchemaFieldValue( break; } case 'RANGE': { - value = BigQueryRange.fromSchemaValue_( + value = fromSchemaValue_( value, schemaField.rangeElementType!.type!, - ); + options.listParams // Required to convert TIMESTAMP values + ) break; } default: @@ -2666,41 +2754,23 @@ export class BigQueryRange { } private static fromStringValue_(value: string): [start: string, end: string] { - let cleanedValue = value; - if (cleanedValue.startsWith('[') || cleanedValue.startsWith('(')) { - cleanedValue = cleanedValue.substring(1); - } - if (cleanedValue.endsWith(')') || cleanedValue.endsWith(']')) { - cleanedValue = cleanedValue.substring(0, cleanedValue.length - 1); - } - const parts = cleanedValue.split(','); - if (parts.length !== 2) { - throw new Error( - 'invalid RANGE. See RANGE literal format docs for more information.', - ); - } - - const [start, end] = parts.map((s: string) => s.trim()); - return [start, end]; + /* + We moved the internals of fromSchemaValue_ to a method outside of this + class, but fromSchemaValue_ calls fromStringValue_ and this fromStringValue_ + method is private so we needed a fromStringValue_ method outside of this + class so it could be accessed by fromSchemaValue_ function. + */ + return fromStringValue_(value); } static fromSchemaValue_(value: string, elementType: string): BigQueryRange { - const [start, end] = BigQueryRange.fromStringValue_(value); - const convertRangeSchemaValue = (value: string) => { - if (value === 'UNBOUNDED' || value === 'NULL') { - return null; - } - return convertSchemaFieldValue({type: elementType}, value, { - wrapIntegers: false, - }); - }; - return BigQuery.range( - { - start: convertRangeSchemaValue(start), - end: convertRangeSchemaValue(end), - }, - elementType, - ); + /* + fromSchemaValue_ functionality needs to pass listParams into + convertSchemaFieldValue. But we didn't want to add another parameter to this + function because it changes the API so the internals of this method were + moved to fromSchemaValue_ which now accepts an extra parameter. + */ + return fromSchemaValue_(value, elementType); } private convertElement_( diff --git a/handwritten/bigquery/src/job.ts b/handwritten/bigquery/src/job.ts index d39f950b7b7..b91839aa1fa 100644 --- a/handwritten/bigquery/src/job.ts +++ b/handwritten/bigquery/src/job.ts @@ -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 + 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; diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 5bbb6f4483a..43143b264cd 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1472,11 +1472,16 @@ describe('BigQuery', () => { ], }, (err, rows) => { - assert.ifError(err); - assert.strictEqual(rows!.length, 1); - done(); - }, - ); + try { + // Without this try block the test runner silently fails + assert.ifError(err); + assert.strictEqual(rows!.length, 1); + done(); + } catch (e) { + done(e); + } + }, + ) }); it('should work with multiple types', done => { diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts new file mode 100644 index 00000000000..e6f87f5d13f --- /dev/null +++ b/handwritten/bigquery/system-test/high-precision-query-tests.ts @@ -0,0 +1,234 @@ +// 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('High Precision Query System Tests', () => { + let bigquery: BigQuery; + const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456789123Z'; + 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)', + 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 () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. Queries using TIMESTAMP_ADD are another example. + */ + const query = { + query: 'SELECT ? as ts', + params: [bigquery.timestamp('2023-01-01T12:00:00.123456789123Z')], + types: ['TIMESTAMP(12)'], + }; + + 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})`, + ); + } + }); + it(`should handle nested ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. + */ + const query = { + query: 'SELECT ? obj', + params: [ + { + nested: { + a: bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), + }, + }, + ], + types: [ + { + nested: { + a: 'TIMESTAMP(12)', + }, + }, + ], + }; + + 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].obj.nested.a.value !== undefined); + assert.strictEqual( + rows[0].obj.nested.a.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})`, + ); + } + }); + }); +}); diff --git a/handwritten/bigquery/test/bigquery.ts b/handwritten/bigquery/test/bigquery.ts index b53a2b8942b..d6d3dbcbbea 100644 --- a/handwritten/bigquery/test/bigquery.ts +++ b/handwritten/bigquery/test/bigquery.ts @@ -3397,7 +3397,7 @@ describe('BigQuery', () => { }, jobCreationMode: 'JOB_CREATION_REQUIRED', formatOptions: { - useInt64Timestamp: true, + timestampOutputFormat: 'ISO8601_STRING', }, }; assert.deepStrictEqual(req, expectedReq); @@ -3416,7 +3416,7 @@ describe('BigQuery', () => { requestId: req.requestId, jobCreationMode: 'JOB_CREATION_OPTIONAL', formatOptions: { - useInt64Timestamp: true, + timestampOutputFormat: 'ISO8601_STRING', }, }; assert.deepStrictEqual(req, expectedReq); From 0c799a2904549a42dbbe6e4e5947a0d777af5265 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Fri, 6 Feb 2026 10:04:19 -0500 Subject: [PATCH 03/14] correct typo in comment --- handwritten/bigquery/src/job.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/handwritten/bigquery/src/job.ts b/handwritten/bigquery/src/job.ts index b91839aa1fa..ddf7497e1cb 100644 --- a/handwritten/bigquery/src/job.ts +++ b/handwritten/bigquery/src/job.ts @@ -597,10 +597,10 @@ class Job extends Operation { if (resp.schema && resp.rows) { try { /* - Without this try/catch block, calls to getRows will hang indefinitely if - 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. + Without this try/catch block, calls to /query endpoint will hang + indefinitely if 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, From b033b9a27b27728711d6c07c3df3f107edaeef3b Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Fri, 6 Feb 2026 10:23:47 -0500 Subject: [PATCH 04/14] picoseconds / not nanoseconds --- .../bigquery/system-test/timestamp_output_format.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/handwritten/bigquery/system-test/timestamp_output_format.ts b/handwritten/bigquery/system-test/timestamp_output_format.ts index 96ede116075..e0972283eae 100644 --- a/handwritten/bigquery/system-test/timestamp_output_format.ts +++ b/handwritten/bigquery/system-test/timestamp_output_format.ts @@ -35,7 +35,7 @@ describe('Timestamp Output Format System Tests', () => { const table = dataset.table(tableId); const insertedTsValue = '2023-01-01T12:00:00.123456789123Z'; const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z'; - const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456789123Z'; + const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; before(async () => { await dataset.create(); @@ -103,14 +103,14 @@ describe('Timestamp Output Format System Tests', () => { name: 'should call getRows with ISO8601_STRING and useInt64Timestamp=false', timestampOutputFormat: 'ISO8601_STRING', useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, + expectedTsValue: expectedTsValuePicoseconds, }, // Additional test cases for undefined combinations { name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp undefined', timestampOutputFormat: undefined, useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, + expectedTsValue: expectedTsValuePicoseconds, }, { name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp=true', @@ -146,7 +146,7 @@ describe('Timestamp Output Format System Tests', () => { name: 'should call getRows with ISO8601_STRING and useInt64Timestamp undefined (expect error)', timestampOutputFormat: 'ISO8601_STRING', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, + expectedTsValue: expectedTsValuePicoseconds, }, ]; From 5871908c4b2d94db11adeceeb7e4ecb94d61cd8c Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Fri, 6 Feb 2026 10:24:41 -0500 Subject: [PATCH 05/14] rename nanoseconds to picoseconds --- .../bigquery/system-test/high-precision-query-tests.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts index e6f87f5d13f..eb95ca05123 100644 --- a/handwritten/bigquery/system-test/high-precision-query-tests.ts +++ b/handwritten/bigquery/system-test/high-precision-query-tests.ts @@ -19,7 +19,7 @@ import {BigQuery} from '../src'; describe('High Precision Query System Tests', () => { let bigquery: BigQuery; const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z'; - const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456789123Z'; + const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; const expectedErrorMessage = 'Cannot specify both timestamp_as_int and timestamp_output_format.'; @@ -76,13 +76,13 @@ describe('High Precision Query System Tests', () => { name: 'TOF: ISO8601_STRING, UI64: false', timestampOutputFormat: 'ISO8601_STRING', useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, + expectedTsValue: expectedTsValuePicoseconds, }, { name: 'TOF: omitted, UI64: omitted (default INT64)', timestampOutputFormat: undefined, useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, + expectedTsValue: expectedTsValuePicoseconds, }, { name: 'TOF: omitted, UI64: true', @@ -118,7 +118,7 @@ describe('High Precision Query System Tests', () => { name: 'TOF: ISO8601_STRING, UI64: omitted (error)', timestampOutputFormat: 'ISO8601_STRING', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, + expectedTsValue: expectedTsValuePicoseconds, }, ]; From d39d0a9348ae353e08484f55c74215bef32029ac Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Fri, 6 Feb 2026 10:26:22 -0500 Subject: [PATCH 06/14] nanoseconds not microseconds --- .../system-test/high-precision-query-tests.ts | 22 +++++++++---------- .../system-test/timestamp_output_format.ts | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts index eb95ca05123..5eb909bcd5e 100644 --- a/handwritten/bigquery/system-test/high-precision-query-tests.ts +++ b/handwritten/bigquery/system-test/high-precision-query-tests.ts @@ -18,7 +18,7 @@ import {BigQuery} from '../src'; describe('High Precision Query System Tests', () => { let bigquery: BigQuery; - const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; const expectedErrorMessage = 'Cannot specify both timestamp_as_int and timestamp_output_format.'; @@ -32,13 +32,13 @@ describe('High Precision Query System Tests', () => { name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: true', timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', useInt64Timestamp: true, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: FLOAT64, UI64: true (error)', @@ -51,19 +51,19 @@ describe('High Precision Query System Tests', () => { name: 'TOF: FLOAT64, UI64: false', timestampOutputFormat: 'FLOAT64', useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: INT64, UI64: true', timestampOutputFormat: 'INT64', useInt64Timestamp: true, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: INT64, UI64: false (error)', timestampOutputFormat: 'INT64', useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: ISO8601_STRING, UI64: true (error)', @@ -88,31 +88,31 @@ describe('High Precision Query System Tests', () => { name: 'TOF: omitted, UI64: true', timestampOutputFormat: undefined, useInt64Timestamp: true, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', timestampOutputFormat: undefined, useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: FLOAT64, UI64: omitted (error)', timestampOutputFormat: 'FLOAT64', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: INT64, UI64: omitted', timestampOutputFormat: 'INT64', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'TOF: ISO8601_STRING, UI64: omitted (error)', diff --git a/handwritten/bigquery/system-test/timestamp_output_format.ts b/handwritten/bigquery/system-test/timestamp_output_format.ts index e0972283eae..0fe388e1e3b 100644 --- a/handwritten/bigquery/system-test/timestamp_output_format.ts +++ b/handwritten/bigquery/system-test/timestamp_output_format.ts @@ -34,7 +34,7 @@ describe('Timestamp Output Format System Tests', () => { const dataset = bigquery.dataset(datasetId); const table = dataset.table(tableId); const insertedTsValue = '2023-01-01T12:00:00.123456789123Z'; - const expectedTsValueMicroseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; before(async () => { @@ -59,13 +59,13 @@ describe('Timestamp Output Format System Tests', () => { name: 'should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp=true', timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', useInt64Timestamp: true, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp=false', timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with FLOAT64 and useInt64Timestamp=true (expect error)', @@ -78,19 +78,19 @@ describe('Timestamp Output Format System Tests', () => { name: 'should call getRows with FLOAT64 and useInt64Timestamp=false', timestampOutputFormat: 'FLOAT64', useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with INT64 and useInt64Timestamp=true', timestampOutputFormat: 'INT64', useInt64Timestamp: true, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with INT64 and useInt64Timestamp=false', timestampOutputFormat: 'INT64', useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with ISO8601_STRING and useInt64Timestamp=true (expect error)', @@ -116,31 +116,31 @@ describe('Timestamp Output Format System Tests', () => { name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp=true', timestampOutputFormat: undefined, useInt64Timestamp: true, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp=false', timestampOutputFormat: undefined, useInt64Timestamp: false, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp undefined', timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with FLOAT64 and useInt64Timestamp undefined (expect error)', timestampOutputFormat: 'FLOAT64', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with INT64 and useInt64Timestamp undefined', timestampOutputFormat: 'INT64', useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueMicroseconds, + expectedTsValue: expectedTsValueNanoseconds, }, { name: 'should call getRows with ISO8601_STRING and useInt64Timestamp undefined (expect error)', From 9b61a746dfc1ed7c241f456118f4a8bc041ef354 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Fri, 6 Feb 2026 10:28:19 -0500 Subject: [PATCH 07/14] Add comment for non-meaningful use case --- .../bigquery/system-test/high-precision-query-tests.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts index 5eb909bcd5e..e4876b0e2b9 100644 --- a/handwritten/bigquery/system-test/high-precision-query-tests.ts +++ b/handwritten/bigquery/system-test/high-precision-query-tests.ts @@ -40,13 +40,15 @@ describe('High Precision Query System Tests', () => { useInt64Timestamp: false, expectedTsValue: expectedTsValueNanoseconds, }, - { + /* + { // This is not a meaningful use case. name: 'TOF: FLOAT64, UI64: true (error)', timestampOutputFormat: 'FLOAT64', useInt64Timestamp: true, expectedTsValue: undefined, expectedError: expectedErrorMessage, }, + */ { name: 'TOF: FLOAT64, UI64: false', timestampOutputFormat: 'FLOAT64', From 4cbde9458a7d4ae6216836cf89b06c0fad6fd62f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:07:35 +0000 Subject: [PATCH 08/14] feat(bigquery): add unit tests for buildQueryRequest_ format options Adds a new test suite to bigquery.ts that verifies the correct construction of QueryRequest formatOptions for various combinations of timestampOutputFormat and useInt64Timestamp. The test cases are modeled after the high-precision-query system tests. Co-authored-by: danieljbruce <8935272+danieljbruce@users.noreply.github.com> --- handwritten/bigquery/test/bigquery.ts | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/handwritten/bigquery/test/bigquery.ts b/handwritten/bigquery/test/bigquery.ts index d6d3dbcbbea..d814b0e35f0 100644 --- a/handwritten/bigquery/test/bigquery.ts +++ b/handwritten/bigquery/test/bigquery.ts @@ -3403,6 +3403,135 @@ describe('BigQuery', () => { assert.deepStrictEqual(req, expectedReq); }); + describe('format options', () => { + const testCases = [ + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: true', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: true, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: false, + }, + { + name: 'TOF: FLOAT64, UI64: false', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: false, + }, + { + name: 'TOF: INT64, UI64: true', + timestampOutputFormat: 'INT64', + useInt64Timestamp: true, + }, + { + name: 'TOF: INT64, UI64: false (error)', + timestampOutputFormat: 'INT64', + useInt64Timestamp: false, + }, + { + name: 'TOF: ISO8601_STRING, UI64: true (error)', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: true, + }, + { + name: 'TOF: ISO8601_STRING, UI64: false', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: false, + }, + { + name: 'TOF: omitted, UI64: omitted (default INT64)', + timestampOutputFormat: undefined, + useInt64Timestamp: undefined, + }, + { + name: 'TOF: omitted, UI64: true', + timestampOutputFormat: undefined, + useInt64Timestamp: true, + }, + { + name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: undefined, + useInt64Timestamp: false, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: undefined, + }, + { + name: 'TOF: FLOAT64, UI64: omitted (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: undefined, + }, + { + name: 'TOF: INT64, UI64: omitted', + timestampOutputFormat: 'INT64', + useInt64Timestamp: undefined, + }, + { + name: 'TOF: ISO8601_STRING, UI64: omitted (error)', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: undefined, + }, + ]; + + testCases.forEach(testCase => { + it(`should handle ${testCase.name}`, () => { + const options: any = {}; + if (testCase.timestampOutputFormat !== undefined) { + options['formatOptions.timestampOutputFormat'] = + testCase.timestampOutputFormat; + } + if (testCase.useInt64Timestamp !== undefined) { + options['formatOptions.useInt64Timestamp'] = + testCase.useInt64Timestamp; + } + + const req = bq.buildQueryRequest_(QUERY_STRING, options); + for (const key in req) { + if (req[key] === undefined) { + delete req[key]; + } + } + if (req.formatOptions) { + for (const key in req.formatOptions) { + if (req.formatOptions[key] === undefined) { + delete req.formatOptions[key]; + } + } + } + + const expectedFormatOptions: any = {}; + if ( + testCase.timestampOutputFormat === undefined && + testCase.useInt64Timestamp === undefined + ) { + expectedFormatOptions.timestampOutputFormat = 'ISO8601_STRING'; + } else { + if (testCase.timestampOutputFormat !== undefined) { + expectedFormatOptions.timestampOutputFormat = + testCase.timestampOutputFormat; + } + if (testCase.useInt64Timestamp !== undefined) { + expectedFormatOptions.useInt64Timestamp = + testCase.useInt64Timestamp; + } + } + + const expectedReq = { + query: QUERY_STRING, + useLegacySql: false, + requestId: req.requestId, + jobCreationMode: 'JOB_CREATION_OPTIONAL', + formatOptions: expectedFormatOptions, + }; + assert.deepStrictEqual(req, expectedReq); + }); + }); + }); + it('should create a QueryRequest from a SQL string', () => { const req = bq.buildQueryRequest_(QUERY_STRING, {}); for (const key in req) { From 00795deb511fea0a191f6a257048c38e8395ba40 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 9 Feb 2026 15:10:37 -0500 Subject: [PATCH 09/14] Move the code back into the BigQueryRange static fns --- handwritten/bigquery/src/bigquery.ts | 121 ++++++++++++--------------- 1 file changed, 52 insertions(+), 69 deletions(-) diff --git a/handwritten/bigquery/src/bigquery.ts b/handwritten/bigquery/src/bigquery.ts index 89596f050b4..f9b5d933a5c 100644 --- a/handwritten/bigquery/src/bigquery.ts +++ b/handwritten/bigquery/src/bigquery.ts @@ -2500,59 +2500,6 @@ promisifyAll(BigQuery, { ], }); -function fromStringValue_(value: string): [start: string, end: string] { - /* - This method returns start and end values for RANGE typed values returned from - the server. It decodes the server RANGE value into start and end values so - they can be used to construct a BigQueryRange. - */ - let cleanedValue = value; - if (cleanedValue.startsWith('[') || cleanedValue.startsWith('(')) { - cleanedValue = cleanedValue.substring(1); - } - if (cleanedValue.endsWith(')') || cleanedValue.endsWith(']')) { - cleanedValue = cleanedValue.substring(0, cleanedValue.length - 1); - } - const parts = cleanedValue.split(','); - if (parts.length !== 2) { - throw new Error( - 'invalid RANGE. See RANGE literal format docs for more information.', - ); - } - - const [start, end] = parts.map((s: string) => s.trim()); - return [start, end]; -} - -function fromSchemaValue_(value: string, elementType: string, listParams?: - | bigquery.tabledata.IListParams - | bigquery.jobs.IGetQueryResultsParams -) { - /* - This method is only used by convertSchemaFieldValue and only when range - values are passed into convertSchemaFieldValue. It produces a value that is - delivered to the user for read calls and it needs to pass along listParams - to ensure TIMESTAMP types are converted properly. - */ - const [start, end] = fromStringValue_(value); - const convertRangeSchemaValue = (value: string) => { - if (value === 'UNBOUNDED' || value === 'NULL') { - return null; - } - return convertSchemaFieldValue({type: elementType}, value, { - wrapIntegers: false, - listParams - }); - }; - return BigQuery.range( - { - start: convertRangeSchemaValue(start), - end: convertRangeSchemaValue(end), - }, - elementType, - ); -} - function convertSchemaFieldValue( schemaField: TableField, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -2672,11 +2619,11 @@ function convertSchemaFieldValue( break; } case 'RANGE': { - value = fromSchemaValue_( + value = BigQueryRange.fromSchemaValue_( value, schemaField.rangeElementType!.type!, - options.listParams // Required to convert TIMESTAMP values - ) + options.listParams, // Required to convert TIMESTAMP values + ); break; } default: @@ -2755,22 +2702,58 @@ export class BigQueryRange { private static fromStringValue_(value: string): [start: string, end: string] { /* - We moved the internals of fromSchemaValue_ to a method outside of this - class, but fromSchemaValue_ calls fromStringValue_ and this fromStringValue_ - method is private so we needed a fromStringValue_ method outside of this - class so it could be accessed by fromSchemaValue_ function. - */ - return fromStringValue_(value); + This method returns start and end values for RANGE typed values returned from + the server. It decodes the server RANGE value into start and end values so + they can be used to construct a BigQueryRange. + */ + let cleanedValue = value; + if (cleanedValue.startsWith('[') || cleanedValue.startsWith('(')) { + cleanedValue = cleanedValue.substring(1); + } + if (cleanedValue.endsWith(')') || cleanedValue.endsWith(']')) { + cleanedValue = cleanedValue.substring(0, cleanedValue.length - 1); + } + const parts = cleanedValue.split(','); + if (parts.length !== 2) { + throw new Error( + 'invalid RANGE. See RANGE literal format docs for more information.', + ); + } + + const [start, end] = parts.map((s: string) => s.trim()); + return [start, end]; } - static fromSchemaValue_(value: string, elementType: string): BigQueryRange { + static fromSchemaValue_( + value: string, + elementType: string, + listParams?: + | bigquery.tabledata.IListParams + | bigquery.jobs.IGetQueryResultsParams, + ): BigQueryRange { /* - fromSchemaValue_ functionality needs to pass listParams into - convertSchemaFieldValue. But we didn't want to add another parameter to this - function because it changes the API so the internals of this method were - moved to fromSchemaValue_ which now accepts an extra parameter. - */ - return fromSchemaValue_(value, elementType); + This method is only used by convertSchemaFieldValue and only when range + values are passed into convertSchemaFieldValue. It produces a value that is + delivered to the user for read calls and it needs to pass along listParams + to ensure TIMESTAMP types are converted properly. + */ + const [start, end] = BigQueryRange.fromStringValue_(value); + const convertRangeSchemaValue = (value: string) => { + if (value === 'UNBOUNDED' || value === 'NULL') { + return null; + } + return convertSchemaFieldValue({type: elementType}, value, { + wrapIntegers: false, + listParams, + }); + }; + return BigQuery.range( + { + start: convertRangeSchemaValue(start), + end: convertRangeSchemaValue(end), + }, + elementType, + ); } private convertElement_( From f042cc44be65b61603651601e5144f649b63a56e Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 9 Feb 2026 15:49:04 -0500 Subject: [PATCH 10/14] =?UTF-8?q?Move=20the=20=E2=80=9CHigh=20Precision=20?= =?UTF-8?q?Query=20System=20Tests=E2=80=9D=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handwritten/bigquery/system-test/bigquery.ts | 230 ++++++++++++++++- .../system-test/high-precision-query-tests.ts | 236 ------------------ 2 files changed, 229 insertions(+), 237 deletions(-) delete mode 100644 handwritten/bigquery/system-test/high-precision-query-tests.ts diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 43143b264cd..52d81acbcc5 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1238,6 +1238,234 @@ describe('BigQuery', () => { describe('SQL parameters', () => { describe('positional', () => { + describe.only('High Precision Query System Tests', () => { + let bigquery: BigQuery; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValuePicoseconds = + '2023-01-01T12:00:00.123456789123Z'; + 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: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + /* + { // This is not a meaningful use case. + name: 'TOF: FLOAT64, UI64: true (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: true, + expectedTsValue: undefined, + expectedError: expectedErrorMessage, + }, + */ + { + name: 'TOF: FLOAT64, UI64: false', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: true', + timestampOutputFormat: 'INT64', + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: false (error)', + timestampOutputFormat: 'INT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + 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: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: omitted (default INT64)', + timestampOutputFormat: undefined, + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: true', + timestampOutputFormat: undefined, + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: undefined, + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: FLOAT64, UI64: omitted (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: omitted', + timestampOutputFormat: 'INT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: ISO8601_STRING, UI64: omitted (error)', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + ]; + + testCases.forEach(testCase => { + it(`should handle ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. Queries using TIMESTAMP_ADD are another example. + */ + const query = { + query: 'SELECT ? as ts', + params: [ + bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), + ], + types: ['TIMESTAMP(12)'], + }; + + 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})`, + ); + } + }); + it(`should handle nested ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. + */ + const query = { + query: 'SELECT ? obj', + params: [ + { + nested: { + a: bigquery.timestamp( + '2023-01-01T12:00:00.123456789123Z', + ), + }, + }, + ], + types: [ + { + nested: { + a: 'TIMESTAMP(12)', + }, + }, + ], + }; + + 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].obj.nested.a.value !== undefined); + assert.strictEqual( + rows[0].obj.nested.a.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})`, + ); + } + }); + }); + }); it('should work with strings', done => { bigquery.query( { @@ -1481,7 +1709,7 @@ describe('BigQuery', () => { done(e); } }, - ) + ); }); it('should work with multiple types', done => { diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts deleted file mode 100644 index e4876b0e2b9..00000000000 --- a/handwritten/bigquery/system-test/high-precision-query-tests.ts +++ /dev/null @@ -1,236 +0,0 @@ -// 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('High Precision Query System Tests', () => { - let bigquery: BigQuery; - const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; - const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; - 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: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - /* - { // This is not a meaningful use case. - name: 'TOF: FLOAT64, UI64: true (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: true, - expectedTsValue: undefined, - expectedError: expectedErrorMessage, - }, - */ - { - name: 'TOF: FLOAT64, UI64: false', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: true', - timestampOutputFormat: 'INT64', - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: false (error)', - timestampOutputFormat: 'INT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - 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: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: omitted (default INT64)', - timestampOutputFormat: undefined, - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: true', - timestampOutputFormat: undefined, - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: undefined, - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: FLOAT64, UI64: omitted (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: omitted', - timestampOutputFormat: 'INT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: ISO8601_STRING, UI64: omitted (error)', - timestampOutputFormat: 'ISO8601_STRING', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - ]; - - testCases.forEach(testCase => { - it(`should handle ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. Queries using TIMESTAMP_ADD are another example. - */ - const query = { - query: 'SELECT ? as ts', - params: [bigquery.timestamp('2023-01-01T12:00:00.123456789123Z')], - types: ['TIMESTAMP(12)'], - }; - - 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})`, - ); - } - }); - it(`should handle nested ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. - */ - const query = { - query: 'SELECT ? obj', - params: [ - { - nested: { - a: bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), - }, - }, - ], - types: [ - { - nested: { - a: 'TIMESTAMP(12)', - }, - }, - ], - }; - - 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].obj.nested.a.value !== undefined); - assert.strictEqual( - rows[0].obj.nested.a.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})`, - ); - } - }); - }); -}); From 8c4c2d535d8e3529a94c6b574c81c8ba80de7385 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 9 Feb 2026 15:58:04 -0500 Subject: [PATCH 11/14] =?UTF-8?q?Revert=20"Move=20the=20=E2=80=9CHigh=20Pr?= =?UTF-8?q?ecision=20Query=20System=20Tests=E2=80=9D=20block"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit f042cc44be65b61603651601e5144f649b63a56e. --- handwritten/bigquery/system-test/bigquery.ts | 230 +---------------- .../system-test/high-precision-query-tests.ts | 236 ++++++++++++++++++ 2 files changed, 237 insertions(+), 229 deletions(-) create mode 100644 handwritten/bigquery/system-test/high-precision-query-tests.ts diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 52d81acbcc5..43143b264cd 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1238,234 +1238,6 @@ describe('BigQuery', () => { describe('SQL parameters', () => { describe('positional', () => { - describe.only('High Precision Query System Tests', () => { - let bigquery: BigQuery; - const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; - const expectedTsValuePicoseconds = - '2023-01-01T12:00:00.123456789123Z'; - 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: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - /* - { // This is not a meaningful use case. - name: 'TOF: FLOAT64, UI64: true (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: true, - expectedTsValue: undefined, - expectedError: expectedErrorMessage, - }, - */ - { - name: 'TOF: FLOAT64, UI64: false', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: true', - timestampOutputFormat: 'INT64', - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: false (error)', - timestampOutputFormat: 'INT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - 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: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: omitted (default INT64)', - timestampOutputFormat: undefined, - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: true', - timestampOutputFormat: undefined, - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: undefined, - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: FLOAT64, UI64: omitted (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: omitted', - timestampOutputFormat: 'INT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: ISO8601_STRING, UI64: omitted (error)', - timestampOutputFormat: 'ISO8601_STRING', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - ]; - - testCases.forEach(testCase => { - it(`should handle ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. Queries using TIMESTAMP_ADD are another example. - */ - const query = { - query: 'SELECT ? as ts', - params: [ - bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), - ], - types: ['TIMESTAMP(12)'], - }; - - 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})`, - ); - } - }); - it(`should handle nested ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. - */ - const query = { - query: 'SELECT ? obj', - params: [ - { - nested: { - a: bigquery.timestamp( - '2023-01-01T12:00:00.123456789123Z', - ), - }, - }, - ], - types: [ - { - nested: { - a: 'TIMESTAMP(12)', - }, - }, - ], - }; - - 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].obj.nested.a.value !== undefined); - assert.strictEqual( - rows[0].obj.nested.a.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})`, - ); - } - }); - }); - }); it('should work with strings', done => { bigquery.query( { @@ -1709,7 +1481,7 @@ describe('BigQuery', () => { done(e); } }, - ); + ) }); it('should work with multiple types', done => { diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts new file mode 100644 index 00000000000..e4876b0e2b9 --- /dev/null +++ b/handwritten/bigquery/system-test/high-precision-query-tests.ts @@ -0,0 +1,236 @@ +// 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('High Precision Query System Tests', () => { + let bigquery: BigQuery; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; + 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: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + /* + { // This is not a meaningful use case. + name: 'TOF: FLOAT64, UI64: true (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: true, + expectedTsValue: undefined, + expectedError: expectedErrorMessage, + }, + */ + { + name: 'TOF: FLOAT64, UI64: false', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: true', + timestampOutputFormat: 'INT64', + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: false (error)', + timestampOutputFormat: 'INT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + 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: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: omitted (default INT64)', + timestampOutputFormat: undefined, + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: true', + timestampOutputFormat: undefined, + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: undefined, + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: FLOAT64, UI64: omitted (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: omitted', + timestampOutputFormat: 'INT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: ISO8601_STRING, UI64: omitted (error)', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + ]; + + testCases.forEach(testCase => { + it(`should handle ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. Queries using TIMESTAMP_ADD are another example. + */ + const query = { + query: 'SELECT ? as ts', + params: [bigquery.timestamp('2023-01-01T12:00:00.123456789123Z')], + types: ['TIMESTAMP(12)'], + }; + + 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})`, + ); + } + }); + it(`should handle nested ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. + */ + const query = { + query: 'SELECT ? obj', + params: [ + { + nested: { + a: bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), + }, + }, + ], + types: [ + { + nested: { + a: 'TIMESTAMP(12)', + }, + }, + ], + }; + + 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].obj.nested.a.value !== undefined); + assert.strictEqual( + rows[0].obj.nested.a.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})`, + ); + } + }); + }); +}); From 48176ddfdb114f02c533c13eedd8e2eebc8426b5 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 9 Feb 2026 16:36:51 -0500 Subject: [PATCH 12/14] =?UTF-8?q?Reapply=20"Move=20the=20=E2=80=9CHigh=20P?= =?UTF-8?q?recision=20Query=20System=20Tests=E2=80=9D=20block"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 8c4c2d535d8e3529a94c6b574c81c8ba80de7385. --- handwritten/bigquery/system-test/bigquery.ts | 230 ++++++++++++++++- .../system-test/high-precision-query-tests.ts | 236 ------------------ 2 files changed, 229 insertions(+), 237 deletions(-) delete mode 100644 handwritten/bigquery/system-test/high-precision-query-tests.ts diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 43143b264cd..52d81acbcc5 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1238,6 +1238,234 @@ describe('BigQuery', () => { describe('SQL parameters', () => { describe('positional', () => { + describe.only('High Precision Query System Tests', () => { + let bigquery: BigQuery; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValuePicoseconds = + '2023-01-01T12:00:00.123456789123Z'; + 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: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + /* + { // This is not a meaningful use case. + name: 'TOF: FLOAT64, UI64: true (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: true, + expectedTsValue: undefined, + expectedError: expectedErrorMessage, + }, + */ + { + name: 'TOF: FLOAT64, UI64: false', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: true', + timestampOutputFormat: 'INT64', + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: false (error)', + timestampOutputFormat: 'INT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + 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: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: omitted (default INT64)', + timestampOutputFormat: undefined, + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: true', + timestampOutputFormat: undefined, + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: undefined, + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: FLOAT64, UI64: omitted (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: omitted', + timestampOutputFormat: 'INT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: ISO8601_STRING, UI64: omitted (error)', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + ]; + + testCases.forEach(testCase => { + it(`should handle ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. Queries using TIMESTAMP_ADD are another example. + */ + const query = { + query: 'SELECT ? as ts', + params: [ + bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), + ], + types: ['TIMESTAMP(12)'], + }; + + 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})`, + ); + } + }); + it(`should handle nested ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. + */ + const query = { + query: 'SELECT ? obj', + params: [ + { + nested: { + a: bigquery.timestamp( + '2023-01-01T12:00:00.123456789123Z', + ), + }, + }, + ], + types: [ + { + nested: { + a: 'TIMESTAMP(12)', + }, + }, + ], + }; + + 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].obj.nested.a.value !== undefined); + assert.strictEqual( + rows[0].obj.nested.a.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})`, + ); + } + }); + }); + }); it('should work with strings', done => { bigquery.query( { @@ -1481,7 +1709,7 @@ describe('BigQuery', () => { done(e); } }, - ) + ); }); it('should work with multiple types', done => { diff --git a/handwritten/bigquery/system-test/high-precision-query-tests.ts b/handwritten/bigquery/system-test/high-precision-query-tests.ts deleted file mode 100644 index e4876b0e2b9..00000000000 --- a/handwritten/bigquery/system-test/high-precision-query-tests.ts +++ /dev/null @@ -1,236 +0,0 @@ -// 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('High Precision Query System Tests', () => { - let bigquery: BigQuery; - const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; - const expectedTsValuePicoseconds = '2023-01-01T12:00:00.123456789123Z'; - 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: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - /* - { // This is not a meaningful use case. - name: 'TOF: FLOAT64, UI64: true (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: true, - expectedTsValue: undefined, - expectedError: expectedErrorMessage, - }, - */ - { - name: 'TOF: FLOAT64, UI64: false', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: true', - timestampOutputFormat: 'INT64', - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: false (error)', - timestampOutputFormat: 'INT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - 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: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: omitted (default INT64)', - timestampOutputFormat: undefined, - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: true', - timestampOutputFormat: undefined, - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: undefined, - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: FLOAT64, UI64: omitted (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: omitted', - timestampOutputFormat: 'INT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: ISO8601_STRING, UI64: omitted (error)', - timestampOutputFormat: 'ISO8601_STRING', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - ]; - - testCases.forEach(testCase => { - it(`should handle ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. Queries using TIMESTAMP_ADD are another example. - */ - const query = { - query: 'SELECT ? as ts', - params: [bigquery.timestamp('2023-01-01T12:00:00.123456789123Z')], - types: ['TIMESTAMP(12)'], - }; - - 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})`, - ); - } - }); - it(`should handle nested ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. - */ - const query = { - query: 'SELECT ? obj', - params: [ - { - nested: { - a: bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), - }, - }, - ], - types: [ - { - nested: { - a: 'TIMESTAMP(12)', - }, - }, - ], - }; - - 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].obj.nested.a.value !== undefined); - assert.strictEqual( - rows[0].obj.nested.a.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})`, - ); - } - }); - }); -}); From 756d77bb9489c5fd4b73e92dd8f9264eecb3552e Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 9 Feb 2026 16:56:35 -0500 Subject: [PATCH 13/14] Move the tests to the end position --- handwritten/bigquery/system-test/bigquery.ts | 456 +++++++++---------- 1 file changed, 228 insertions(+), 228 deletions(-) diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 52d81acbcc5..337534348f3 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1238,234 +1238,6 @@ describe('BigQuery', () => { describe('SQL parameters', () => { describe('positional', () => { - describe.only('High Precision Query System Tests', () => { - let bigquery: BigQuery; - const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; - const expectedTsValuePicoseconds = - '2023-01-01T12:00:00.123456789123Z'; - 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: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - /* - { // This is not a meaningful use case. - name: 'TOF: FLOAT64, UI64: true (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: true, - expectedTsValue: undefined, - expectedError: expectedErrorMessage, - }, - */ - { - name: 'TOF: FLOAT64, UI64: false', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: true', - timestampOutputFormat: 'INT64', - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: false (error)', - timestampOutputFormat: 'INT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - 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: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: omitted (default INT64)', - timestampOutputFormat: undefined, - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - { - name: 'TOF: omitted, UI64: true', - timestampOutputFormat: undefined, - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: undefined, - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: FLOAT64, UI64: omitted (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: omitted', - timestampOutputFormat: 'INT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: ISO8601_STRING, UI64: omitted (error)', - timestampOutputFormat: 'ISO8601_STRING', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, - ]; - - testCases.forEach(testCase => { - it(`should handle ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. Queries using TIMESTAMP_ADD are another example. - */ - const query = { - query: 'SELECT ? as ts', - params: [ - bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), - ], - types: ['TIMESTAMP(12)'], - }; - - 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})`, - ); - } - }); - it(`should handle nested ${testCase.name}`, async () => { - /* - The users use the new TIMESTAMP(12) type to indicate they want to - opt in to using timestampPrecision=12. The reason is that some queries - like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set - timestampPrecision=12 and we don't want this code change to affect - existing users. - */ - const query = { - query: 'SELECT ? obj', - params: [ - { - nested: { - a: bigquery.timestamp( - '2023-01-01T12:00:00.123456789123Z', - ), - }, - }, - ], - types: [ - { - nested: { - a: 'TIMESTAMP(12)', - }, - }, - ], - }; - - 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].obj.nested.a.value !== undefined); - assert.strictEqual( - rows[0].obj.nested.a.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})`, - ); - } - }); - }); - }); it('should work with strings', done => { bigquery.query( { @@ -1731,6 +1503,234 @@ describe('BigQuery', () => { }, ); }); + describe.only('High Precision Query System Tests', () => { + let bigquery: BigQuery; + const expectedTsValueNanoseconds = '2023-01-01T12:00:00.123456000Z'; + const expectedTsValuePicoseconds = + '2023-01-01T12:00:00.123456789123Z'; + 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: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + /* + { // This is not a meaningful use case. + name: 'TOF: FLOAT64, UI64: true (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: true, + expectedTsValue: undefined, + expectedError: expectedErrorMessage, + }, + */ + { + name: 'TOF: FLOAT64, UI64: false', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: true', + timestampOutputFormat: 'INT64', + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: false (error)', + timestampOutputFormat: 'INT64', + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + 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: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: omitted (default INT64)', + timestampOutputFormat: undefined, + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + { + name: 'TOF: omitted, UI64: true', + timestampOutputFormat: undefined, + useInt64Timestamp: true, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', + timestampOutputFormat: undefined, + useInt64Timestamp: false, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', + timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: FLOAT64, UI64: omitted (error)', + timestampOutputFormat: 'FLOAT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: INT64, UI64: omitted', + timestampOutputFormat: 'INT64', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValueNanoseconds, + }, + { + name: 'TOF: ISO8601_STRING, UI64: omitted (error)', + timestampOutputFormat: 'ISO8601_STRING', + useInt64Timestamp: undefined, + expectedTsValue: expectedTsValuePicoseconds, + }, + ]; + + testCases.forEach(testCase => { + it(`should handle ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. Queries using TIMESTAMP_ADD are another example. + */ + const query = { + query: 'SELECT ? as ts', + params: [ + bigquery.timestamp('2023-01-01T12:00:00.123456789123Z'), + ], + types: ['TIMESTAMP(12)'], + }; + + 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})`, + ); + } + }); + it(`should handle nested ${testCase.name}`, async () => { + /* + The users use the new TIMESTAMP(12) type to indicate they want to + opt in to using timestampPrecision=12. The reason is that some queries + like `SELECT CAST(? as TIMESTAMP(12))` will fail if we set + timestampPrecision=12 and we don't want this code change to affect + existing users. + */ + const query = { + query: 'SELECT ? obj', + params: [ + { + nested: { + a: bigquery.timestamp( + '2023-01-01T12:00:00.123456789123Z', + ), + }, + }, + ], + types: [ + { + nested: { + a: 'TIMESTAMP(12)', + }, + }, + ], + }; + + 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].obj.nested.a.value !== undefined); + assert.strictEqual( + rows[0].obj.nested.a.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})`, + ); + } + }); + }); + }); }); describe('named', () => { From 4ad12337adc37ee837a541689c0603fecd89ca78 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 9 Feb 2026 17:29:08 -0500 Subject: [PATCH 14/14] delete unwanted test cases --- handwritten/bigquery/system-test/bigquery.ts | 75 -------------------- 1 file changed, 75 deletions(-) diff --git a/handwritten/bigquery/system-test/bigquery.ts b/handwritten/bigquery/system-test/bigquery.ts index 337534348f3..96c3a33a53a 100644 --- a/handwritten/bigquery/system-test/bigquery.ts +++ b/handwritten/bigquery/system-test/bigquery.ts @@ -1517,56 +1517,11 @@ describe('BigQuery', () => { const testCases = [ { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: true', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - /* - { // This is not a meaningful use case. name: 'TOF: FLOAT64, UI64: true (error)', timestampOutputFormat: 'FLOAT64', useInt64Timestamp: true, expectedTsValue: undefined, expectedError: expectedErrorMessage, - }, - */ - { - name: 'TOF: FLOAT64, UI64: false', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: true', - timestampOutputFormat: 'INT64', - useInt64Timestamp: true, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: false (error)', - timestampOutputFormat: 'INT64', - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - 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: expectedTsValuePicoseconds, }, { name: 'TOF: omitted, UI64: omitted (default INT64)', @@ -1580,36 +1535,6 @@ describe('BigQuery', () => { useInt64Timestamp: true, expectedTsValue: expectedTsValueNanoseconds, }, - { - name: 'TOF: omitted, UI64: false (default ISO8601_STRING)', - timestampOutputFormat: undefined, - useInt64Timestamp: false, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED, UI64: omitted (default INT64)', - timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: FLOAT64, UI64: omitted (error)', - timestampOutputFormat: 'FLOAT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: INT64, UI64: omitted', - timestampOutputFormat: 'INT64', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValueNanoseconds, - }, - { - name: 'TOF: ISO8601_STRING, UI64: omitted (error)', - timestampOutputFormat: 'ISO8601_STRING', - useInt64Timestamp: undefined, - expectedTsValue: expectedTsValuePicoseconds, - }, ]; testCases.forEach(testCase => {