-
Notifications
You must be signed in to change notification settings - Fork 210
feat: support high precision timestamp strings on getRows calls #1596
Changes from 56 commits
a6dd325
173e65a
489be58
a1f1bba
a0f7fd3
e6b03c1
e934164
9567663
b9e81ac
da842e5
38148e1
c52c9f4
f0ab80e
07aa7f1
1b09a9c
1ff7a2c
c206170
d83f2a5
c131f4e
8c4efef
4366e9e
074d05f
5fb38d4
7e5d6b0
e5506e9
ad2211a
3d072d0
44e6e7b
dbea718
14bb0a8
1cf914e
02601d3
feb0f9a
26a4f07
6e9de44
c45bde8
bdde5d4
7ebd6ee
a17f421
fb38379
506edb8
efacb53
41c5948
33f05ab
6913244
d53b6c5
00b8bc7
1077b8e
a1bbfc1
08b0b62
0547069
d199177
629b519
cd5d35a
7de53d4
3df3939
1b27a26
eaaa4d9
6f58be2
678ff01
5e094af
703de99
1ae67bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| // 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, after} from 'mocha'; | ||
| import {BigQuery} from '../src/bigquery'; | ||
| import {randomUUID} from 'crypto'; | ||
| import {RequestResponse} from '@google-cloud/common/build/src/service-object'; | ||
|
|
||
| const bigquery = new BigQuery(); | ||
|
|
||
| interface TestCase { | ||
| name: string; | ||
| timestampOutputFormat?: string; | ||
| useInt64Timestamp?: boolean; | ||
| expectedError?: string; | ||
| expectedTsValue?: string; | ||
| } | ||
|
|
||
| describe('Timestamp Output Format System Tests', () => { | ||
| const datasetId = `timestamp_test_${randomUUID().replace(/-/g, '_')}`; | ||
| const tableId = `timestamp_table_${randomUUID().replace(/-/g, '_')}`; | ||
| 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.123456789123Z'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we already test Picosecond resolution or do we need the update from PreciseDate ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the question, but I'll try to answer it. It appears that with the current code changes that whenever we specify |
||
|
|
||
| before(async () => { | ||
| await dataset.create(); | ||
| await table.create({ | ||
| schema: [{name: 'ts', type: 'TIMESTAMP', timestampPrecision: '12'}], | ||
| }); | ||
| // Insert a row to test retrieval | ||
| await table.insert([{ts: insertedTsValue}]); | ||
| }); | ||
|
|
||
| after(async () => { | ||
| try { | ||
| await dataset.delete({force: true}); | ||
| } catch (e) { | ||
| console.error('Error deleting dataset:', e); | ||
| } | ||
| }); | ||
|
|
||
| const testCases: TestCase[] = [ | ||
| { | ||
| name: 'should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp=true', | ||
| timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', | ||
| useInt64Timestamp: true, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp=false', | ||
| timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', | ||
| useInt64Timestamp: false, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with FLOAT64 and useInt64Timestamp=true (expect error)', | ||
| timestampOutputFormat: 'FLOAT64', | ||
| useInt64Timestamp: true, | ||
| expectedError: | ||
| 'Cannot specify both use_int64_timestamp and timestamp_output_format.', | ||
| }, | ||
| { | ||
| name: 'should call getRows with FLOAT64 and useInt64Timestamp=false', | ||
| timestampOutputFormat: 'FLOAT64', | ||
| useInt64Timestamp: false, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with INT64 and useInt64Timestamp=true', | ||
| timestampOutputFormat: 'INT64', | ||
| useInt64Timestamp: true, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with INT64 and useInt64Timestamp=false', | ||
| timestampOutputFormat: 'INT64', | ||
| useInt64Timestamp: false, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with ISO8601_STRING and useInt64Timestamp=true (expect error)', | ||
| timestampOutputFormat: 'ISO8601_STRING', | ||
| useInt64Timestamp: true, | ||
| expectedError: | ||
| 'Cannot specify both use_int64_timestamp and timestamp_output_format.', | ||
| }, | ||
| { | ||
| name: 'should call getRows with ISO8601_STRING and useInt64Timestamp=false', | ||
| timestampOutputFormat: 'ISO8601_STRING', | ||
| useInt64Timestamp: false, | ||
| expectedTsValue: expectedTsValueNanoseconds, | ||
| }, | ||
| // Additional test cases for undefined combinations | ||
| { | ||
| name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp undefined', | ||
alvarowolfx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| timestampOutputFormat: undefined, | ||
| useInt64Timestamp: undefined, | ||
| expectedTsValue: expectedTsValueNanoseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp=true', | ||
| timestampOutputFormat: undefined, | ||
| useInt64Timestamp: true, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with timestampOutputFormat undefined and useInt64Timestamp=false', | ||
| timestampOutputFormat: undefined, | ||
| useInt64Timestamp: false, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp undefined', | ||
| timestampOutputFormat: 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', | ||
| useInt64Timestamp: undefined, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with FLOAT64 and useInt64Timestamp undefined (expect error)', | ||
| timestampOutputFormat: 'FLOAT64', | ||
| useInt64Timestamp: undefined, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with INT64 and useInt64Timestamp undefined', | ||
| timestampOutputFormat: 'INT64', | ||
| useInt64Timestamp: undefined, | ||
| expectedTsValue: expectedTsValueMicroseconds, | ||
| }, | ||
| { | ||
| name: 'should call getRows with ISO8601_STRING and useInt64Timestamp undefined (expect error)', | ||
| timestampOutputFormat: 'ISO8601_STRING', | ||
| useInt64Timestamp: undefined, | ||
| expectedTsValue: expectedTsValueNanoseconds, | ||
| }, | ||
| ]; | ||
|
|
||
| testCases.forEach( | ||
| ({ | ||
| name, | ||
| timestampOutputFormat, | ||
| useInt64Timestamp, | ||
| expectedError, | ||
| expectedTsValue, | ||
| }) => { | ||
| it(name, async () => { | ||
| const options: {[key: string]: any} = {}; | ||
| if (timestampOutputFormat !== undefined) { | ||
| options['formatOptions.timestampOutputFormat'] = | ||
| timestampOutputFormat; | ||
| } | ||
| if (useInt64Timestamp !== undefined) { | ||
| options['formatOptions.useInt64Timestamp'] = useInt64Timestamp; | ||
| } | ||
|
|
||
| if (expectedError) { | ||
| try { | ||
| await table.getRows(options); | ||
| assert.fail('The call should have thrown an error.'); | ||
| } catch (e) { | ||
| assert.strictEqual((e as Error).message, expectedError); | ||
| } | ||
| } else { | ||
| const [rows] = await table.getRows(options); | ||
| assert(rows.length > 0); | ||
| assert.strictEqual(rows[0].ts.value, expectedTsValue); | ||
| } | ||
| }); | ||
| }, | ||
| ); | ||
|
|
||
| it.only('should make a request with ISO8601_STRING when no format options are being used', done => { | ||
| (async () => { | ||
| const originalRequest = table.request; | ||
| const requestPromise: Promise<RequestResponse> = new Promise((resolve, reject) => { | ||
| const innerPromise = new Promise((innerResolve, innerReject) => { | ||
| innerResolve({}); | ||
| }); | ||
| resolve(innerPromise as Promise<RequestResponse>); | ||
| }) | ||
| table.request = (reqOpts) => { | ||
| table.request = originalRequest; | ||
| if (reqOpts.qs['formatOptions.timestampOutputFormat'] === 'ISO8601_STRING') { | ||
| done(); | ||
| } else { | ||
| done(new Error('The default timestampOutputFormat should be ISO8601_STRING')); | ||
| } | ||
| return requestPromise; | ||
| } | ||
| await table.getRows({}); | ||
| })(); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2046,7 +2046,8 @@ describe('BigQuery/Table', () => { | |
| assert.strictEqual(reqOpts.uri, '/data'); | ||
| assert.deepStrictEqual(reqOpts.qs, { | ||
| ...options, | ||
| 'formatOptions.useInt64Timestamp': true, | ||
| 'formatOptions.useInt64Timestamp': false, | ||
|
||
| 'formatOptions.timestampOutputFormat': 'ISO8601_STRING', | ||
| }); | ||
| callback(null, {}); | ||
| }; | ||
|
|
@@ -2208,7 +2209,8 @@ describe('BigQuery/Table', () => { | |
| assert.deepStrictEqual(nextQuery, { | ||
| a: 'b', | ||
| c: 'd', | ||
| 'formatOptions.useInt64Timestamp': true, | ||
| 'formatOptions.useInt64Timestamp': false, | ||
| 'formatOptions.timestampOutputFormat': 'ISO8601_STRING', | ||
| pageToken, | ||
| }); | ||
| // Original object isn't affected. | ||
|
|
@@ -2425,7 +2427,8 @@ describe('BigQuery/Table', () => { | |
|
|
||
| table.request = (reqOpts: DecorateRequestOptions, callback: Function) => { | ||
| assert.deepStrictEqual(reqOpts.qs, { | ||
| 'formatOptions.useInt64Timestamp': true, | ||
| 'formatOptions.useInt64Timestamp': false, | ||
| 'formatOptions.timestampOutputFormat': 'ISO8601_STRING', | ||
| }); | ||
| callback(null, {}); | ||
| }; | ||
|
|
@@ -2449,7 +2452,8 @@ describe('BigQuery/Table', () => { | |
|
|
||
| table.request = (reqOpts: DecorateRequestOptions, callback: Function) => { | ||
| assert.deepStrictEqual(reqOpts.qs, { | ||
| 'formatOptions.useInt64Timestamp': true, | ||
| 'formatOptions.useInt64Timestamp': false, | ||
| 'formatOptions.timestampOutputFormat': 'ISO8601_STRING', | ||
| }); | ||
| callback(null, {}); | ||
| }; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.