diff --git a/src/bigquery.ts b/src/bigquery.ts index 65fc01ef..281fe1a0 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -56,6 +56,7 @@ import { } from '@google-cloud/common/build/src/util'; import bigquery from './types'; import {logger, setLogFunction} from './logger'; +import IListParams = bigquery.jobs.IListParams; // Third-Party Re-exports export {common}; @@ -596,7 +597,7 @@ export class BigQuery extends Service { wrapIntegers: boolean | IntegerTypeCastOptions; selectedFields?: string[]; parseJSON?: boolean; - useInt64Timestamp?: boolean; + listParams?: bigquery.tabledata.IListParams; }, ) { // deep copy schema fields to avoid mutation @@ -2472,7 +2473,7 @@ function convertSchemaFieldValue( wrapIntegers: boolean | IntegerTypeCastOptions; selectedFields?: string[]; parseJSON?: boolean; - useInt64Timestamp?: boolean; + listParams?: bigquery.tabledata.IListParams; }, ) { if (value === null) { @@ -2540,9 +2541,21 @@ function convertSchemaFieldValue( 1672574400.123456 2023-01-01T12:00:00.123456789123Z */ - const pd = new PreciseDate(); - pd.setFullTime(PreciseDate.parseFull(BigInt(value) * BigInt(1000))); - value = BigQuery.timestamp(pd); + const listParams = options.listParams; + const timestampOutputFormat = listParams ? listParams['formatOptions.timestampOutputFormat'] : undefined; + const useInt64Timestamp = listParams ? listParams['formatOptions.useInt64Timestamp'] : undefined; + if (timestampOutputFormat === 'ISO8601_STRING') { + // value is ISO string, create BigQueryTimestamp wrapping the string + value = BigQuery.timestamp(value); + } else if (useInt64Timestamp === false && timestampOutputFormat !== 'INT64') { + // value is float seconds, convert to BigQueryTimestamp + value = BigQuery.timestamp(Number(value)); + } else { + // Expect int64 micros (default or explicit INT64) + const pd = new PreciseDate(); + pd.setFullTime(PreciseDate.parseFull(BigInt(value) * BigInt(1000))); + value = BigQuery.timestamp(pd); + } break; } case 'GEOGRAPHY': { @@ -2736,6 +2749,7 @@ export class BigQueryTimestamp { pd = new PreciseDate(value); } else if (typeof value === 'string') { if (/^\d{4}-\d{1,2}-\d{1,2}/.test(value)) { + // TODO: Replace with logic here that allows for higher precision. pd = new PreciseDate(value); } else { const floatValue = Number.parseFloat(value); diff --git a/src/job.ts b/src/job.ts index a9219b2e..d39f950b 100644 --- a/src/job.ts +++ b/src/job.ts @@ -598,7 +598,6 @@ class Job extends Operation { rows = BigQuery.mergeSchemaWithRows_(resp.schema, resp.rows, { wrapIntegers, parseJSON, - useInt64Timestamp: qs['formatOptions.useInt64Timestamp'] }); } diff --git a/src/table.ts b/src/table.ts index becc9b25..59e1d96e 100644 --- a/src/table.ts +++ b/src/table.ts @@ -55,6 +55,7 @@ import {JobMetadata, JobOptions} from './job'; import bigquery from './types'; import {IntegerTypeCastOptions} from './bigquery'; import {RowQueue} from './rowQueue'; +import IDataFormatOptions = bigquery.IDataFormatOptions; // This is supposed to be a @google-cloud/storage `File` type. The storage npm // module includes these types, but is current installed as a devDependency. @@ -1876,7 +1877,7 @@ class Table extends ServiceObject { wrapIntegers, selectedFields, parseJSON, - useInt64Timestamp: qs['formatOptions.useInt64Timestamp'], + listParams: qs, }); } catch (err) { diff --git a/system-test/timestamp_output_format.ts b/system-test/timestamp_output_format.ts index a8f2c2b6..46b8fdd3 100644 --- a/system-test/timestamp_output_format.ts +++ b/system-test/timestamp_output_format.ts @@ -53,18 +53,15 @@ describe.only('Timestamp Output Format System Tests', () => { }); it('should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp=false', async () => { - try { - await table.getRows({ - 'formatOptions.timestampOutputFormat': 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', - 'formatOptions.useInt64Timestamp': false - }); - assert.fail('The call should not have succeeded'); - } catch (e) { - assert.strictEqual((e as Error).message, 'Cannot convert 1672574400.123456 to a BigInt'); - } + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED', + 'formatOptions.useInt64Timestamp': false + }); + assert.strictEqual(rows[0].ts.value, expectedValue); }); it('should call getRows with FLOAT64 and useInt64Timestamp=true', async () => { + // Step through this one. try { const [rows] = await table.getRows({ 'formatOptions.timestampOutputFormat': 'FLOAT64', @@ -77,17 +74,12 @@ describe.only('Timestamp Output Format System Tests', () => { }); it('should call getRows with FLOAT64 and useInt64Timestamp=false', async () => { - try { - const [rows] = await table.getRows({ - 'formatOptions.timestampOutputFormat': 'FLOAT64', - 'formatOptions.useInt64Timestamp': false - }); - assert(rows.length > 0); - assert.strictEqual(rows[0].ts.value, expectedValue); - assert.fail('The call should not have succeeded'); - } catch (e) { - assert.strictEqual((e as Error).message, 'Cannot convert 1672574400.123456 to a BigInt'); - } + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'FLOAT64', + 'formatOptions.useInt64Timestamp': false + }); + assert(rows.length > 0); + assert.strictEqual(rows[0].ts.value, expectedValue); }); it('should call getRows with INT64 and useInt64Timestamp=true', async () => { @@ -122,15 +114,70 @@ describe.only('Timestamp Output Format System Tests', () => { } }); - it('should call getRows with ISO8601_STRING and useInt64Timestamp=false', async () => { + it.skip('should call getRows with ISO8601_STRING and useInt64Timestamp=false', async () => { + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'ISO8601_STRING', + 'formatOptions.useInt64Timestamp': false + }); + assert.strictEqual(rows[0].ts.value, '2023-01-01T12:00:00.123456789123'); + }); + + it('should call getRows with timestampOutputFormat undefined and useInt64Timestamp=true', async () => { + const [rows] = await table.getRows({ + 'formatOptions.useInt64Timestamp': true + }); + assert(rows.length > 0); + assert.strictEqual(rows[0].ts.value, expectedValue); + }); + + it('should call getRows with timestampOutputFormat undefined and useInt64Timestamp=false', async () => { + const [rows] = await table.getRows({ + 'formatOptions.useInt64Timestamp': false + }); + assert.strictEqual(rows[0].ts.value, expectedValue); + }); + + it('should call getRows with TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED and useInt64Timestamp undefined', async () => { + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'TIMESTAMP_OUTPUT_FORMAT_UNSPECIFIED' + }); + assert(rows.length > 0); + assert.strictEqual(rows[0].ts.value, expectedValue); + }); + + it('should call getRows with FLOAT64 and useInt64Timestamp undefined', async () => { + try { + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'FLOAT64' + }); + assert.fail('The call should not have succeeded'); + } catch (e) { + assert.strictEqual((e as Error).message, 'Cannot specify both use_int64_timestamp and timestamp_output_format.'); + } + }); + + it('should call getRows with INT64 and useInt64Timestamp undefined', async () => { + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'INT64' + }); + assert(rows.length > 0); + assert.strictEqual(rows[0].ts.value, expectedValue); + }); + + it('should call getRows with ISO8601_STRING and useInt64Timestamp undefined', async () => { try { - await table.getRows({ - 'formatOptions.timestampOutputFormat': 'ISO8601_STRING', - 'formatOptions.useInt64Timestamp': false + const [rows] = await table.getRows({ + 'formatOptions.timestampOutputFormat': 'ISO8601_STRING' }); assert.fail('The call should not have succeeded'); } catch (e) { - assert.strictEqual((e as Error).message, 'Cannot convert 2023-01-01T12:00:00.123456789123Z to a BigInt'); + assert.strictEqual((e as Error).message, 'Cannot specify both use_int64_timestamp and timestamp_output_format.'); } }); + + it('should call getRows with timestampOutputFormat undefined and useInt64Timestamp undefined', async () => { + const [rows] = await table.getRows({}); + assert(rows.length > 0); + assert.strictEqual(rows[0].ts.value, expectedValue); + }); });