From 139242258dd614fcc226ad8f6db58618d9e8b453 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Mon, 22 Apr 2024 12:27:28 -0700 Subject: [PATCH 1/4] feat: add support for RANGE type (#1352) --- src/bigquery.ts | 391 ++++++++++++++++++++++++++++++---------- src/table.ts | 3 +- system-test/bigquery.ts | 62 ++++++- test/bigquery.ts | 223 +++++++++++++++++++++++ test/table.ts | 9 + 5 files changed, 586 insertions(+), 102 deletions(-) diff --git a/src/bigquery.ts b/src/bigquery.ts index 58c0e4416..9b1ab73cd 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -191,6 +191,11 @@ export interface BigQueryDatetimeOptions { fractional?: string | number; } +export interface BigQueryRangeOptions { + start?: BigQueryDate | BigQueryDatetime | BigQueryTimestamp | string; + end?: BigQueryDate | BigQueryDatetime | BigQueryTimestamp | string; +} + export type ProvidedTypeArray = Array; export interface ProvidedTypeStruct { @@ -582,10 +587,10 @@ export class BigQuery extends Service { let value = field.v; if (schemaField.mode === 'REPEATED') { value = (value as TableRowField[]).map(val => { - return convert(schemaField, val.v, options); + return convertSchemaFieldValue(schemaField, val.v, options); }); } else { - value = convert(schemaField, value, options); + value = convertSchemaFieldValue(schemaField, value, options); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const fieldObject: any = {}; @@ -594,98 +599,6 @@ export class BigQuery extends Service { }); } - function convert( - schemaField: TableField, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any, - options: { - wrapIntegers: boolean | IntegerTypeCastOptions; - selectedFields?: string[]; - parseJSON?: boolean; - } - ) { - if (is.null(value)) { - return value; - } - - switch (schemaField.type) { - case 'BOOLEAN': - case 'BOOL': { - value = value.toLowerCase() === 'true'; - break; - } - case 'BYTES': { - value = Buffer.from(value, 'base64'); - break; - } - case 'FLOAT': - case 'FLOAT64': { - value = Number(value); - break; - } - case 'INTEGER': - case 'INT64': { - const {wrapIntegers} = options; - value = wrapIntegers - ? typeof wrapIntegers === 'object' - ? BigQuery.int( - {integerValue: value, schemaFieldName: schemaField.name}, - wrapIntegers - ).valueOf() - : BigQuery.int(value) - : Number(value); - break; - } - case 'NUMERIC': { - value = new Big(value); - break; - } - case 'BIGNUMERIC': { - value = new Big(value); - break; - } - case 'RECORD': { - value = BigQuery.mergeSchemaWithRows_( - schemaField, - value, - options - ).pop(); - break; - } - case 'DATE': { - value = BigQuery.date(value); - break; - } - case 'DATETIME': { - value = BigQuery.datetime(value); - break; - } - case 'TIME': { - value = BigQuery.time(value); - break; - } - case 'TIMESTAMP': { - const pd = new PreciseDate(); - pd.setFullTime(PreciseDate.parseFull(BigInt(value) * BigInt(1000))); - value = BigQuery.timestamp(pd); - break; - } - case 'GEOGRAPHY': { - value = BigQuery.geography(value); - break; - } - case 'JSON': { - const {parseJSON} = options; - value = parseJSON ? JSON.parse(value) : value; - break; - } - default: - break; - } - - return value; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any function flattenRows(rows: any[]) { return rows.reduce((acc, row) => { @@ -946,6 +859,47 @@ export class BigQuery extends Service { return BigQuery.timestamp(value); } + /** + * A range represents contiguous range between two dates, datetimes, or timestamps. + * The lower and upper bound for the range are optional. + * The lower bound is inclusive and the upper bound is exclusive. + * + * @method BigQuery.range + * @param {string|BigQueryRangeOptions} value The range API string or start/end with dates/datetimes/timestamp ranges. + * @param {string} elementType The range element type - DATE|DATETIME|TIMESTAMP + * + * @example + * ``` + * const {BigQuery} = require('@google-cloud/bigquery'); + * const timestampRange = BigQuery.range('[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', 'TIMESTAMP'); + * ``` + */ + static range( + value: string | BigQueryRangeOptions, + elementType?: string + ): BigQueryRange { + return new BigQueryRange(value, elementType); + } + + /** + * A range represents contiguous range between two dates, datetimes, or timestamps. + * The lower and upper bound for the range are optional. + * The lower bound is inclusive and the upper bound is exclusive. + * + * @param {string|BigQueryRangeOptions} value The range API string or start/end with dates/datetimes/timestamp ranges. + * @param {string} elementType The range element type - DATE|DATETIME|TIMESTAMP + * + * @example + * ``` + * const {BigQuery} = require('@google-cloud/bigquery'); + * const bigquery = new BigQuery(); + * const timestampRange = bigquery.range('[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', 'TIMESTAMP'); + * ``` + */ + range(value: string, elementType?: string): BigQueryRange { + return BigQuery.range(value, elementType); + } + /** * A BigQueryInt wraps 'INT64' values. Can be used to maintain precision. * @@ -1135,6 +1089,13 @@ export class BigQuery extends Service { typeName = 'INT64'; } else if (value instanceof Geography) { typeName = 'GEOGRAPHY'; + } else if (value instanceof BigQueryRange) { + return { + type: 'RANGE', + rangeElementType: { + type: value.elementType, + }, + }; } else if (Array.isArray(value)) { if (value.length === 0) { throw new Error( @@ -1241,6 +1202,24 @@ export class BigQuery extends Service { }, {} ); + } else if (typeName === 'RANGE') { + let rangeValue: BigQueryRange; + if (value instanceof BigQueryRange) { + rangeValue = value; + } else { + rangeValue = BigQuery.range( + value, + queryParameter.parameterType?.rangeElementType?.type + ); + } + queryParameter.parameterValue!.rangeValue = { + start: { + value: rangeValue.value.start, + }, + end: { + value: rangeValue.value.end, + }, + }; } else if (typeName === 'JSON' && is.object(value)) { queryParameter.parameterValue!.value = JSON.stringify(value); } else { @@ -1267,6 +1246,7 @@ export class BigQuery extends Service { type!.indexOf('TIME') > -1 || type!.indexOf('DATE') > -1 || type!.indexOf('GEOGRAPHY') > -1 || + type!.indexOf('RANGE') > -1 || type!.indexOf('BigQueryInt') > -1 ); } @@ -2391,9 +2371,232 @@ promisifyAll(BigQuery, { 'job', 'time', 'timestamp', + 'range', ], }); +function convertSchemaFieldValue( + schemaField: TableField, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any, + options: { + wrapIntegers: boolean | IntegerTypeCastOptions; + selectedFields?: string[]; + parseJSON?: boolean; + } +) { + if (is.null(value)) { + return value; + } + + switch (schemaField.type) { + case 'BOOLEAN': + case 'BOOL': { + value = value.toLowerCase() === 'true'; + break; + } + case 'BYTES': { + value = Buffer.from(value, 'base64'); + break; + } + case 'FLOAT': + case 'FLOAT64': { + value = Number(value); + break; + } + case 'INTEGER': + case 'INT64': { + const {wrapIntegers} = options; + value = wrapIntegers + ? typeof wrapIntegers === 'object' + ? BigQuery.int( + {integerValue: value, schemaFieldName: schemaField.name}, + wrapIntegers + ).valueOf() + : BigQuery.int(value) + : Number(value); + break; + } + case 'NUMERIC': { + value = new Big(value); + break; + } + case 'BIGNUMERIC': { + value = new Big(value); + break; + } + case 'RECORD': { + value = BigQuery.mergeSchemaWithRows_(schemaField, value, options).pop(); + break; + } + case 'DATE': { + value = BigQuery.date(value); + break; + } + case 'DATETIME': { + value = BigQuery.datetime(value); + break; + } + case 'TIME': { + value = BigQuery.time(value); + break; + } + case 'TIMESTAMP': { + const pd = new PreciseDate(); + pd.setFullTime(PreciseDate.parseFull(BigInt(value) * BigInt(1000))); + value = BigQuery.timestamp(pd); + break; + } + case 'GEOGRAPHY': { + value = BigQuery.geography(value); + break; + } + case 'JSON': { + const {parseJSON} = options; + value = parseJSON ? JSON.parse(value) : value; + break; + } + case 'RANGE': { + value = BigQueryRange.fromSchemaValue_( + value, + schemaField.rangeElementType!.type! + ); + break; + } + default: + break; + } + + return value; +} + +/** + * Range class for BigQuery. + * A range represents contiguous range between two dates, datetimes, or timestamps. + * The lower and upper bound for the range are optional. + * The lower bound is inclusive and the upper bound is exclusive. + * See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#range_literals + */ +export class BigQueryRange { + elementType?: string; + start?: BigQueryTimestamp | BigQueryDate | BigQueryDatetime; + end?: BigQueryTimestamp | BigQueryDate | BigQueryDatetime; + + constructor(value: string | BigQueryRangeOptions, elementType?: string) { + if (typeof value === 'string') { + if (!elementType) { + throw new Error( + 'invalid RANGE. Element type required when using RANGE API string.' + ); + } + + const [start, end] = BigQueryRange.fromStringValue_(value); + this.start = this.convertElement_(start, elementType); + this.end = this.convertElement_(end, elementType); + this.elementType = elementType; + } else { + const {start, end} = value; + if (start && end) { + if (typeof start !== typeof end) { + throw Error( + 'upper and lower bound on a RANGE should be of the same type.' + ); + } + } + const inferredType = + { + BigQueryDate: 'DATE', + BigQueryDatetime: 'DATETIME', + BigQueryTimestamp: 'TIMESTAMP', + }[(start || end || Object).constructor.name] || elementType; + this.start = this.convertElement_(start, inferredType); + this.end = this.convertElement_(end, inferredType); + this.elementType = inferredType; + } + } + + /* + * Get Range string representation used by the BigQuery API. + */ + public get apiValue() { + return `[${this.start ? this.start.value : 'UNBOUNDED'}, ${this.end ? this.end.value : 'UNBOUNDED'})`; + } + + /* + * Get Range literal representation accordingly to + * https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#range_literals + */ + public get literalValue() { + return `RANGE<${this.elementType}> ${this.apiValue}`; + } + + public get value() { + return { + start: this.start ? this.start.value : 'UNBOUNDED', + end: this.end ? this.end.value : 'UNBOUNDED', + }; + } + + 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]; + } + + 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 + ); + } + + private convertElement_( + value?: string | BigQueryDate | BigQueryDatetime | BigQueryTimestamp, + elementType?: string + ) { + if (typeof value === 'string') { + if (value === 'UNBOUNDED' || value === 'NULL') { + return undefined; + } + switch (elementType) { + case 'DATE': + return new BigQueryDate(value); + case 'DATETIME': + return new BigQueryDatetime(value); + case 'TIMESTAMP': + return new BigQueryTimestamp(value); + } + return undefined; + } + return value; + } +} + /** * Date class for BigQuery. */ diff --git a/src/table.ts b/src/table.ts index 6897bebb5..dd30eca2a 100644 --- a/src/table.ts +++ b/src/table.ts @@ -52,7 +52,7 @@ import {GoogleErrorBody} from '@google-cloud/common/build/src/util'; import {Duplex, Writable} from 'stream'; import {JobMetadata} from './job'; import bigquery from './types'; -import {IntegerTypeCastOptions} from './bigquery'; +import {BigQueryRange, IntegerTypeCastOptions} from './bigquery'; import {RowQueue} from './rowQueue'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -582,6 +582,7 @@ class Table extends ServiceObject { 'BigQueryInt', 'BigQueryTime', 'BigQueryTimestamp', + 'BigQueryRange', 'Geography', ]; const constructorName = value.constructor?.name; diff --git a/system-test/bigquery.ts b/system-test/bigquery.ts index fa3b21d5e..0e6b9253e 100644 --- a/system-test/bigquery.ts +++ b/system-test/bigquery.ts @@ -1332,6 +1332,25 @@ describe('BigQuery', () => { ); }); + it('should work with RANGE types', done => { + bigquery.query( + { + query: 'SELECT ? r', + params: [ + bigquery.range( + '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', + 'TIMESTAMP' + ), + ], + }, + (err, rows) => { + assert.ifError(err); + assert.strictEqual(rows!.length, 1); + done(); + } + ); + }); + it('should work with multiple types', done => { bigquery.query( { @@ -1602,6 +1621,25 @@ describe('BigQuery', () => { ); }); + it('should work with RANGE types', done => { + bigquery.query( + { + query: 'SELECT @r r', + params: { + r: bigquery.range( + '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', + 'TIMESTAMP' + ), + }, + }, + (err, rows) => { + assert.ifError(err); + assert.strictEqual(rows!.length, 1); + done(); + } + ); + }); + it('should work with multiple types', done => { bigquery.query( { @@ -1659,18 +1697,27 @@ describe('BigQuery', () => { const TIMESTAMP = bigquery.timestamp(new Date()); const NUMERIC = new Big('123.456'); const GEOGRAPHY = bigquery.geography('POINT(1 2)'); + const RANGE = bigquery.range( + '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', + 'TIMESTAMP' + ); before(() => { table = dataset.table(generateName('table')); return table.create({ schema: [ - 'date:DATE', - 'datetime:DATETIME', - 'time:TIME', - 'timestamp:TIMESTAMP', - 'numeric:NUMERIC', - 'geography:GEOGRAPHY', - ].join(', '), + {name: 'date', type: 'DATE'}, + {name: 'datetime', type: 'DATETIME'}, + {name: 'time', type: 'TIME'}, + {name: 'timestamp', type: 'TIMESTAMP'}, + {name: 'numeric', type: 'NUMERIC'}, + {name: 'geography', type: 'GEOGRAPHY'}, + { + name: 'range', + type: 'RANGE', + rangeElementType: {type: 'TIMESTAMP'}, + }, + ], }); }); @@ -1682,6 +1729,7 @@ describe('BigQuery', () => { timestamp: TIMESTAMP, numeric: NUMERIC, geography: GEOGRAPHY, + range: RANGE, }); }); }); diff --git a/test/bigquery.ts b/test/bigquery.ts index ebebb722a..4d760931c 100644 --- a/test/bigquery.ts +++ b/test/bigquery.ts @@ -89,6 +89,7 @@ const fakePfy = Object.assign({}, pfy, { 'job', 'time', 'timestamp', + 'range', ]); }, }); @@ -462,6 +463,14 @@ describe('BigQuery', () => { input, }; }); + + sandbox.stub(BigQuery, 'range').callsFake((input, elementType) => { + return { + type: 'fakeRange', + input, + elementType, + }; + }); }); it('should merge the schema and flatten the rows', () => { @@ -520,6 +529,7 @@ describe('BigQuery', () => { {v: 'datetime-input'}, {v: 'time-input'}, {v: 'geography-input'}, + {v: '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)'}, ], }, expected: { @@ -562,6 +572,20 @@ describe('BigQuery', () => { input: 'geography-input', type: 'fakeGeography', }, + range: { + type: 'fakeRange', + input: { + end: { + input: '2020-12-31 12:00:00+08', + type: 'fakeDatetime', + }, + start: { + input: '2020-10-01 12:00:00+08', + type: 'fakeDatetime', + }, + }, + elementType: 'DATETIME', + }, }, }, ]; @@ -629,6 +653,14 @@ describe('BigQuery', () => { type: 'GEOGRAPHY', }); + schemaObject.fields.push({ + name: 'range', + type: 'RANGE', + rangeElementType: { + type: 'DATETIME', + }, + }); + const rawRows = rows.map(x => x.raw); const mergedRows = BigQuery.mergeSchemaWithRows_(schemaObject, rawRows, { wrapIntegers: false, @@ -942,6 +974,186 @@ describe('BigQuery', () => { }); }); + describe('range', () => { + const INPUT_DATE_RANGE = '[2020-01-01, 2020-12-31)'; + const INPUT_DATETIME_RANGE = '[2020-01-01 12:00:00, 2020-12-31 12:00:00)'; + const INPUT_TIMESTAMP_RANGE = + '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)'; + + it('should have the correct constructor name', () => { + const range = bq.range(INPUT_DATE_RANGE, 'DATE'); + assert.strictEqual(range.constructor.name, 'BigQueryRange'); + }); + + it('should accept a string literal', () => { + const dateRange = bq.range(INPUT_DATE_RANGE, 'DATE'); + assert.strictEqual(dateRange.apiValue, '[2020-01-01, 2020-12-31)'); + assert.strictEqual( + dateRange.literalValue, + 'RANGE [2020-01-01, 2020-12-31)' + ); + assert.deepStrictEqual(dateRange.value, { + start: '2020-01-01', + end: '2020-12-31', + }); + + const datetimeRange = bq.range(INPUT_DATETIME_RANGE, 'DATETIME'); + assert.strictEqual( + datetimeRange.apiValue, + '[2020-01-01 12:00:00, 2020-12-31 12:00:00)' + ); + assert.strictEqual( + datetimeRange.literalValue, + 'RANGE [2020-01-01 12:00:00, 2020-12-31 12:00:00)' + ); + assert.deepStrictEqual(datetimeRange.value, { + start: '2020-01-01 12:00:00', + end: '2020-12-31 12:00:00', + }); + + const timestampRange = bq.range(INPUT_TIMESTAMP_RANGE, 'TIMESTAMP'); + assert.strictEqual( + timestampRange.apiValue, + '[2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' + ); + assert.strictEqual( + timestampRange.literalValue, + 'RANGE [2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' + ); + assert.deepStrictEqual(timestampRange.value, { + start: '2020-10-01T04:00:00.000Z', + end: '2020-12-31T04:00:00.000Z', + }); + }); + + it('should accept a BigQueryDate|BigQueryDatetime|BigQueryTimestamp objects', () => { + const dateRange = bq.range({ + start: bq.date('2020-01-01'), + end: bq.date('2020-12-31'), + }); + assert.strictEqual(dateRange.apiValue, INPUT_DATE_RANGE); + assert.strictEqual( + dateRange.literalValue, + `RANGE ${INPUT_DATE_RANGE}` + ); + assert.strictEqual(dateRange.elementType, 'DATE'); + assert.deepStrictEqual(dateRange.value, { + start: '2020-01-01', + end: '2020-12-31', + }); + + const datetimeRange = bq.range({ + start: bq.datetime('2020-01-01 12:00:00'), + end: bq.datetime('2020-12-31 12:00:00'), + }); + assert.strictEqual(datetimeRange.apiValue, INPUT_DATETIME_RANGE); + assert.strictEqual( + datetimeRange.literalValue, + `RANGE ${INPUT_DATETIME_RANGE}` + ); + assert.strictEqual(datetimeRange.elementType, 'DATETIME'); + assert.deepStrictEqual(datetimeRange.value, { + start: '2020-01-01 12:00:00', + end: '2020-12-31 12:00:00', + }); + + const timestampRange = bq.range({ + start: bq.timestamp('2020-10-01 12:00:00+08'), + end: bq.timestamp('2020-12-31 12:00:00+08'), + }); + assert.strictEqual( + timestampRange.apiValue, + '[2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' + ); + assert.strictEqual( + timestampRange.literalValue, + 'RANGE [2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' + ); + assert.strictEqual(timestampRange.elementType, 'TIMESTAMP'); + assert.deepStrictEqual(timestampRange.value, { + start: '2020-10-01T04:00:00.000Z', + end: '2020-12-31T04:00:00.000Z', + }); + }); + + it('should accept a start/end as string with element type', () => { + const dateRange = bq.range( + { + start: '2020-01-01', + end: '2020-12-31', + }, + 'DATE' + ); + assert.strictEqual(dateRange.apiValue, INPUT_DATE_RANGE); + assert.strictEqual( + dateRange.literalValue, + `RANGE ${INPUT_DATE_RANGE}` + ); + assert.strictEqual(dateRange.elementType, 'DATE'); + + const datetimeRange = bq.range( + { + start: '2020-01-01 12:00:00', + end: '2020-12-31 12:00:00', + }, + 'DATETIME' + ); + assert.strictEqual(datetimeRange.apiValue, INPUT_DATETIME_RANGE); + assert.strictEqual( + datetimeRange.literalValue, + `RANGE ${INPUT_DATETIME_RANGE}` + ); + assert.strictEqual(datetimeRange.elementType, 'DATETIME'); + + const timestampRange = bq.range( + { + start: '2020-10-01 12:00:00+08', + end: '2020-12-31 12:00:00+08', + }, + 'TIMESTAMP' + ); + assert.strictEqual( + timestampRange.apiValue, + '[2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' + ); + assert.strictEqual( + timestampRange.literalValue, + 'RANGE [2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' + ); + assert.strictEqual(timestampRange.elementType, 'TIMESTAMP'); + }); + + it('should accept a Range with start and/or end missing', () => { + const dateRange = bq.range( + { + start: '2020-01-01', + }, + 'DATE' + ); + assert.strictEqual( + dateRange.literalValue, + 'RANGE [2020-01-01, UNBOUNDED)' + ); + + const datetimeRange = bq.range( + { + end: '2020-12-31 12:00:00', + }, + 'DATETIME' + ); + assert.strictEqual( + datetimeRange.literalValue, + 'RANGE [UNBOUNDED, 2020-12-31 12:00:00)' + ); + + const timestampRange = bq.range({}, 'TIMESTAMP'); + assert.strictEqual( + timestampRange.literalValue, + 'RANGE [UNBOUNDED, UNBOUNDED)' + ); + }); + }); + describe('geography', () => { const INPUT_STRING = 'POINT(1 2)'; @@ -1216,6 +1428,15 @@ describe('BigQuery', () => { BigQuery.getTypeDescriptorFromValue_(bq.geography('POINT (1 1')).type, 'GEOGRAPHY' ); + assert.strictEqual( + BigQuery.getTypeDescriptorFromValue_( + bq.range( + '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', + 'TIMESTAMP' + ) + ).type, + 'RANGE' + ); }); it('should return correct type for an array', () => { @@ -1656,10 +1877,12 @@ describe('BigQuery', () => { const time = {type: 'TIME'}; const date = {type: 'DATE'}; const geo = {type: 'GEOGRAPHY'}; + const range = {type: 'RANGE'}; assert.strictEqual(BigQuery._isCustomType(time), true); assert.strictEqual(BigQuery._isCustomType(date), true); assert.strictEqual(BigQuery._isCustomType(geo), true); + assert.strictEqual(BigQuery._isCustomType(range), true); }); }); }); diff --git a/test/table.ts b/test/table.ts index 84d71ce5e..47d269f87 100644 --- a/test/table.ts +++ b/test/table.ts @@ -346,6 +346,15 @@ describe('BigQuery/Table', () => { const date = new Date(); assert.strictEqual(Table.encodeValue_(date), date.toJSON()); + + const range = BigQuery.range( + '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', + 'TIMESTAMP' + ); + assert.deepEqual(Table.encodeValue_(range), { + start: '2020-10-01T04:00:00.000Z', + end: '2020-12-31T04:00:00.000Z', + }); }); it('should properly encode custom types', () => { From 2b66d1e5d57d754d188e8b45d8f58de0beff39b3 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Fri, 26 Apr 2024 08:56:01 -0700 Subject: [PATCH 2/4] fix: should not override queryParameters if params not informed (#1359) --- src/bigquery.ts | 16 +++++++++------- test/bigquery.ts | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/bigquery.ts b/src/bigquery.ts index 9b1ab73cd..9a4b8fff0 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -1469,13 +1469,15 @@ export class BigQuery extends Service { delete query.destination; } - const {parameterMode, params} = this.buildQueryParams_( - query.params, - query.types - ); - query.parameterMode = parameterMode; - query.queryParameters = params; - delete query.params; + if (query.params) { + const {parameterMode, params} = this.buildQueryParams_( + query.params, + query.types + ); + query.parameterMode = parameterMode; + query.queryParameters = params; + delete query.params; + } const reqOpts: JobOptions = {}; reqOpts.configuration = { diff --git a/test/bigquery.ts b/test/bigquery.ts index 4d760931c..1c40684e3 100644 --- a/test/bigquery.ts +++ b/test/bigquery.ts @@ -2343,6 +2343,26 @@ describe('BigQuery', () => { ); }); + it('should not modify queryParameters if params is not informed', done => { + bq.createJob = (reqOpts: JobOptions) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + assert.strictEqual((reqOpts as any).params, undefined); + assert.deepStrictEqual( + reqOpts.configuration?.query?.queryParameters, + NAMED_PARAMS + ); + done(); + }; + + bq.createQueryJob( + { + query: QUERY_STRING, + queryParameters: NAMED_PARAMS, + }, + assert.ifError + ); + }); + describe('named', () => { it('should set the correct parameter mode', done => { bq.createJob = (reqOpts: JobOptions) => { From 092bf17b8283e01568602f5539b5a5d3c49fd798 Mon Sep 17 00:00:00 2001 From: Alvaro Viebrantz Date: Fri, 26 Apr 2024 09:08:20 -0700 Subject: [PATCH 3/4] Revert "feat: add support for RANGE type (#1352)" (#1358) This reverts commit 139242258dd614fcc226ad8f6db58618d9e8b453. --- src/bigquery.ts | 391 ++++++++++------------------------------ src/table.ts | 3 +- system-test/bigquery.ts | 62 +------ test/bigquery.ts | 223 ----------------------- test/table.ts | 9 - 5 files changed, 102 insertions(+), 586 deletions(-) diff --git a/src/bigquery.ts b/src/bigquery.ts index 9a4b8fff0..3bda8dbb0 100644 --- a/src/bigquery.ts +++ b/src/bigquery.ts @@ -191,11 +191,6 @@ export interface BigQueryDatetimeOptions { fractional?: string | number; } -export interface BigQueryRangeOptions { - start?: BigQueryDate | BigQueryDatetime | BigQueryTimestamp | string; - end?: BigQueryDate | BigQueryDatetime | BigQueryTimestamp | string; -} - export type ProvidedTypeArray = Array; export interface ProvidedTypeStruct { @@ -587,10 +582,10 @@ export class BigQuery extends Service { let value = field.v; if (schemaField.mode === 'REPEATED') { value = (value as TableRowField[]).map(val => { - return convertSchemaFieldValue(schemaField, val.v, options); + return convert(schemaField, val.v, options); }); } else { - value = convertSchemaFieldValue(schemaField, value, options); + value = convert(schemaField, value, options); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const fieldObject: any = {}; @@ -599,6 +594,98 @@ export class BigQuery extends Service { }); } + function convert( + schemaField: TableField, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any, + options: { + wrapIntegers: boolean | IntegerTypeCastOptions; + selectedFields?: string[]; + parseJSON?: boolean; + } + ) { + if (is.null(value)) { + return value; + } + + switch (schemaField.type) { + case 'BOOLEAN': + case 'BOOL': { + value = value.toLowerCase() === 'true'; + break; + } + case 'BYTES': { + value = Buffer.from(value, 'base64'); + break; + } + case 'FLOAT': + case 'FLOAT64': { + value = Number(value); + break; + } + case 'INTEGER': + case 'INT64': { + const {wrapIntegers} = options; + value = wrapIntegers + ? typeof wrapIntegers === 'object' + ? BigQuery.int( + {integerValue: value, schemaFieldName: schemaField.name}, + wrapIntegers + ).valueOf() + : BigQuery.int(value) + : Number(value); + break; + } + case 'NUMERIC': { + value = new Big(value); + break; + } + case 'BIGNUMERIC': { + value = new Big(value); + break; + } + case 'RECORD': { + value = BigQuery.mergeSchemaWithRows_( + schemaField, + value, + options + ).pop(); + break; + } + case 'DATE': { + value = BigQuery.date(value); + break; + } + case 'DATETIME': { + value = BigQuery.datetime(value); + break; + } + case 'TIME': { + value = BigQuery.time(value); + break; + } + case 'TIMESTAMP': { + const pd = new PreciseDate(); + pd.setFullTime(PreciseDate.parseFull(BigInt(value) * BigInt(1000))); + value = BigQuery.timestamp(pd); + break; + } + case 'GEOGRAPHY': { + value = BigQuery.geography(value); + break; + } + case 'JSON': { + const {parseJSON} = options; + value = parseJSON ? JSON.parse(value) : value; + break; + } + default: + break; + } + + return value; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any function flattenRows(rows: any[]) { return rows.reduce((acc, row) => { @@ -859,47 +946,6 @@ export class BigQuery extends Service { return BigQuery.timestamp(value); } - /** - * A range represents contiguous range between two dates, datetimes, or timestamps. - * The lower and upper bound for the range are optional. - * The lower bound is inclusive and the upper bound is exclusive. - * - * @method BigQuery.range - * @param {string|BigQueryRangeOptions} value The range API string or start/end with dates/datetimes/timestamp ranges. - * @param {string} elementType The range element type - DATE|DATETIME|TIMESTAMP - * - * @example - * ``` - * const {BigQuery} = require('@google-cloud/bigquery'); - * const timestampRange = BigQuery.range('[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', 'TIMESTAMP'); - * ``` - */ - static range( - value: string | BigQueryRangeOptions, - elementType?: string - ): BigQueryRange { - return new BigQueryRange(value, elementType); - } - - /** - * A range represents contiguous range between two dates, datetimes, or timestamps. - * The lower and upper bound for the range are optional. - * The lower bound is inclusive and the upper bound is exclusive. - * - * @param {string|BigQueryRangeOptions} value The range API string or start/end with dates/datetimes/timestamp ranges. - * @param {string} elementType The range element type - DATE|DATETIME|TIMESTAMP - * - * @example - * ``` - * const {BigQuery} = require('@google-cloud/bigquery'); - * const bigquery = new BigQuery(); - * const timestampRange = bigquery.range('[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', 'TIMESTAMP'); - * ``` - */ - range(value: string, elementType?: string): BigQueryRange { - return BigQuery.range(value, elementType); - } - /** * A BigQueryInt wraps 'INT64' values. Can be used to maintain precision. * @@ -1089,13 +1135,6 @@ export class BigQuery extends Service { typeName = 'INT64'; } else if (value instanceof Geography) { typeName = 'GEOGRAPHY'; - } else if (value instanceof BigQueryRange) { - return { - type: 'RANGE', - rangeElementType: { - type: value.elementType, - }, - }; } else if (Array.isArray(value)) { if (value.length === 0) { throw new Error( @@ -1202,24 +1241,6 @@ export class BigQuery extends Service { }, {} ); - } else if (typeName === 'RANGE') { - let rangeValue: BigQueryRange; - if (value instanceof BigQueryRange) { - rangeValue = value; - } else { - rangeValue = BigQuery.range( - value, - queryParameter.parameterType?.rangeElementType?.type - ); - } - queryParameter.parameterValue!.rangeValue = { - start: { - value: rangeValue.value.start, - }, - end: { - value: rangeValue.value.end, - }, - }; } else if (typeName === 'JSON' && is.object(value)) { queryParameter.parameterValue!.value = JSON.stringify(value); } else { @@ -1246,7 +1267,6 @@ export class BigQuery extends Service { type!.indexOf('TIME') > -1 || type!.indexOf('DATE') > -1 || type!.indexOf('GEOGRAPHY') > -1 || - type!.indexOf('RANGE') > -1 || type!.indexOf('BigQueryInt') > -1 ); } @@ -2373,232 +2393,9 @@ promisifyAll(BigQuery, { 'job', 'time', 'timestamp', - 'range', ], }); -function convertSchemaFieldValue( - schemaField: TableField, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value: any, - options: { - wrapIntegers: boolean | IntegerTypeCastOptions; - selectedFields?: string[]; - parseJSON?: boolean; - } -) { - if (is.null(value)) { - return value; - } - - switch (schemaField.type) { - case 'BOOLEAN': - case 'BOOL': { - value = value.toLowerCase() === 'true'; - break; - } - case 'BYTES': { - value = Buffer.from(value, 'base64'); - break; - } - case 'FLOAT': - case 'FLOAT64': { - value = Number(value); - break; - } - case 'INTEGER': - case 'INT64': { - const {wrapIntegers} = options; - value = wrapIntegers - ? typeof wrapIntegers === 'object' - ? BigQuery.int( - {integerValue: value, schemaFieldName: schemaField.name}, - wrapIntegers - ).valueOf() - : BigQuery.int(value) - : Number(value); - break; - } - case 'NUMERIC': { - value = new Big(value); - break; - } - case 'BIGNUMERIC': { - value = new Big(value); - break; - } - case 'RECORD': { - value = BigQuery.mergeSchemaWithRows_(schemaField, value, options).pop(); - break; - } - case 'DATE': { - value = BigQuery.date(value); - break; - } - case 'DATETIME': { - value = BigQuery.datetime(value); - break; - } - case 'TIME': { - value = BigQuery.time(value); - break; - } - case 'TIMESTAMP': { - const pd = new PreciseDate(); - pd.setFullTime(PreciseDate.parseFull(BigInt(value) * BigInt(1000))); - value = BigQuery.timestamp(pd); - break; - } - case 'GEOGRAPHY': { - value = BigQuery.geography(value); - break; - } - case 'JSON': { - const {parseJSON} = options; - value = parseJSON ? JSON.parse(value) : value; - break; - } - case 'RANGE': { - value = BigQueryRange.fromSchemaValue_( - value, - schemaField.rangeElementType!.type! - ); - break; - } - default: - break; - } - - return value; -} - -/** - * Range class for BigQuery. - * A range represents contiguous range between two dates, datetimes, or timestamps. - * The lower and upper bound for the range are optional. - * The lower bound is inclusive and the upper bound is exclusive. - * See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#range_literals - */ -export class BigQueryRange { - elementType?: string; - start?: BigQueryTimestamp | BigQueryDate | BigQueryDatetime; - end?: BigQueryTimestamp | BigQueryDate | BigQueryDatetime; - - constructor(value: string | BigQueryRangeOptions, elementType?: string) { - if (typeof value === 'string') { - if (!elementType) { - throw new Error( - 'invalid RANGE. Element type required when using RANGE API string.' - ); - } - - const [start, end] = BigQueryRange.fromStringValue_(value); - this.start = this.convertElement_(start, elementType); - this.end = this.convertElement_(end, elementType); - this.elementType = elementType; - } else { - const {start, end} = value; - if (start && end) { - if (typeof start !== typeof end) { - throw Error( - 'upper and lower bound on a RANGE should be of the same type.' - ); - } - } - const inferredType = - { - BigQueryDate: 'DATE', - BigQueryDatetime: 'DATETIME', - BigQueryTimestamp: 'TIMESTAMP', - }[(start || end || Object).constructor.name] || elementType; - this.start = this.convertElement_(start, inferredType); - this.end = this.convertElement_(end, inferredType); - this.elementType = inferredType; - } - } - - /* - * Get Range string representation used by the BigQuery API. - */ - public get apiValue() { - return `[${this.start ? this.start.value : 'UNBOUNDED'}, ${this.end ? this.end.value : 'UNBOUNDED'})`; - } - - /* - * Get Range literal representation accordingly to - * https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#range_literals - */ - public get literalValue() { - return `RANGE<${this.elementType}> ${this.apiValue}`; - } - - public get value() { - return { - start: this.start ? this.start.value : 'UNBOUNDED', - end: this.end ? this.end.value : 'UNBOUNDED', - }; - } - - 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]; - } - - 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 - ); - } - - private convertElement_( - value?: string | BigQueryDate | BigQueryDatetime | BigQueryTimestamp, - elementType?: string - ) { - if (typeof value === 'string') { - if (value === 'UNBOUNDED' || value === 'NULL') { - return undefined; - } - switch (elementType) { - case 'DATE': - return new BigQueryDate(value); - case 'DATETIME': - return new BigQueryDatetime(value); - case 'TIMESTAMP': - return new BigQueryTimestamp(value); - } - return undefined; - } - return value; - } -} - /** * Date class for BigQuery. */ diff --git a/src/table.ts b/src/table.ts index dd30eca2a..6897bebb5 100644 --- a/src/table.ts +++ b/src/table.ts @@ -52,7 +52,7 @@ import {GoogleErrorBody} from '@google-cloud/common/build/src/util'; import {Duplex, Writable} from 'stream'; import {JobMetadata} from './job'; import bigquery from './types'; -import {BigQueryRange, IntegerTypeCastOptions} from './bigquery'; +import {IntegerTypeCastOptions} from './bigquery'; import {RowQueue} from './rowQueue'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -582,7 +582,6 @@ class Table extends ServiceObject { 'BigQueryInt', 'BigQueryTime', 'BigQueryTimestamp', - 'BigQueryRange', 'Geography', ]; const constructorName = value.constructor?.name; diff --git a/system-test/bigquery.ts b/system-test/bigquery.ts index 0e6b9253e..fa3b21d5e 100644 --- a/system-test/bigquery.ts +++ b/system-test/bigquery.ts @@ -1332,25 +1332,6 @@ describe('BigQuery', () => { ); }); - it('should work with RANGE types', done => { - bigquery.query( - { - query: 'SELECT ? r', - params: [ - bigquery.range( - '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', - 'TIMESTAMP' - ), - ], - }, - (err, rows) => { - assert.ifError(err); - assert.strictEqual(rows!.length, 1); - done(); - } - ); - }); - it('should work with multiple types', done => { bigquery.query( { @@ -1621,25 +1602,6 @@ describe('BigQuery', () => { ); }); - it('should work with RANGE types', done => { - bigquery.query( - { - query: 'SELECT @r r', - params: { - r: bigquery.range( - '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', - 'TIMESTAMP' - ), - }, - }, - (err, rows) => { - assert.ifError(err); - assert.strictEqual(rows!.length, 1); - done(); - } - ); - }); - it('should work with multiple types', done => { bigquery.query( { @@ -1697,27 +1659,18 @@ describe('BigQuery', () => { const TIMESTAMP = bigquery.timestamp(new Date()); const NUMERIC = new Big('123.456'); const GEOGRAPHY = bigquery.geography('POINT(1 2)'); - const RANGE = bigquery.range( - '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', - 'TIMESTAMP' - ); before(() => { table = dataset.table(generateName('table')); return table.create({ schema: [ - {name: 'date', type: 'DATE'}, - {name: 'datetime', type: 'DATETIME'}, - {name: 'time', type: 'TIME'}, - {name: 'timestamp', type: 'TIMESTAMP'}, - {name: 'numeric', type: 'NUMERIC'}, - {name: 'geography', type: 'GEOGRAPHY'}, - { - name: 'range', - type: 'RANGE', - rangeElementType: {type: 'TIMESTAMP'}, - }, - ], + 'date:DATE', + 'datetime:DATETIME', + 'time:TIME', + 'timestamp:TIMESTAMP', + 'numeric:NUMERIC', + 'geography:GEOGRAPHY', + ].join(', '), }); }); @@ -1729,7 +1682,6 @@ describe('BigQuery', () => { timestamp: TIMESTAMP, numeric: NUMERIC, geography: GEOGRAPHY, - range: RANGE, }); }); }); diff --git a/test/bigquery.ts b/test/bigquery.ts index 1c40684e3..f21d43bed 100644 --- a/test/bigquery.ts +++ b/test/bigquery.ts @@ -89,7 +89,6 @@ const fakePfy = Object.assign({}, pfy, { 'job', 'time', 'timestamp', - 'range', ]); }, }); @@ -463,14 +462,6 @@ describe('BigQuery', () => { input, }; }); - - sandbox.stub(BigQuery, 'range').callsFake((input, elementType) => { - return { - type: 'fakeRange', - input, - elementType, - }; - }); }); it('should merge the schema and flatten the rows', () => { @@ -529,7 +520,6 @@ describe('BigQuery', () => { {v: 'datetime-input'}, {v: 'time-input'}, {v: 'geography-input'}, - {v: '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)'}, ], }, expected: { @@ -572,20 +562,6 @@ describe('BigQuery', () => { input: 'geography-input', type: 'fakeGeography', }, - range: { - type: 'fakeRange', - input: { - end: { - input: '2020-12-31 12:00:00+08', - type: 'fakeDatetime', - }, - start: { - input: '2020-10-01 12:00:00+08', - type: 'fakeDatetime', - }, - }, - elementType: 'DATETIME', - }, }, }, ]; @@ -653,14 +629,6 @@ describe('BigQuery', () => { type: 'GEOGRAPHY', }); - schemaObject.fields.push({ - name: 'range', - type: 'RANGE', - rangeElementType: { - type: 'DATETIME', - }, - }); - const rawRows = rows.map(x => x.raw); const mergedRows = BigQuery.mergeSchemaWithRows_(schemaObject, rawRows, { wrapIntegers: false, @@ -974,186 +942,6 @@ describe('BigQuery', () => { }); }); - describe('range', () => { - const INPUT_DATE_RANGE = '[2020-01-01, 2020-12-31)'; - const INPUT_DATETIME_RANGE = '[2020-01-01 12:00:00, 2020-12-31 12:00:00)'; - const INPUT_TIMESTAMP_RANGE = - '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)'; - - it('should have the correct constructor name', () => { - const range = bq.range(INPUT_DATE_RANGE, 'DATE'); - assert.strictEqual(range.constructor.name, 'BigQueryRange'); - }); - - it('should accept a string literal', () => { - const dateRange = bq.range(INPUT_DATE_RANGE, 'DATE'); - assert.strictEqual(dateRange.apiValue, '[2020-01-01, 2020-12-31)'); - assert.strictEqual( - dateRange.literalValue, - 'RANGE [2020-01-01, 2020-12-31)' - ); - assert.deepStrictEqual(dateRange.value, { - start: '2020-01-01', - end: '2020-12-31', - }); - - const datetimeRange = bq.range(INPUT_DATETIME_RANGE, 'DATETIME'); - assert.strictEqual( - datetimeRange.apiValue, - '[2020-01-01 12:00:00, 2020-12-31 12:00:00)' - ); - assert.strictEqual( - datetimeRange.literalValue, - 'RANGE [2020-01-01 12:00:00, 2020-12-31 12:00:00)' - ); - assert.deepStrictEqual(datetimeRange.value, { - start: '2020-01-01 12:00:00', - end: '2020-12-31 12:00:00', - }); - - const timestampRange = bq.range(INPUT_TIMESTAMP_RANGE, 'TIMESTAMP'); - assert.strictEqual( - timestampRange.apiValue, - '[2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' - ); - assert.strictEqual( - timestampRange.literalValue, - 'RANGE [2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' - ); - assert.deepStrictEqual(timestampRange.value, { - start: '2020-10-01T04:00:00.000Z', - end: '2020-12-31T04:00:00.000Z', - }); - }); - - it('should accept a BigQueryDate|BigQueryDatetime|BigQueryTimestamp objects', () => { - const dateRange = bq.range({ - start: bq.date('2020-01-01'), - end: bq.date('2020-12-31'), - }); - assert.strictEqual(dateRange.apiValue, INPUT_DATE_RANGE); - assert.strictEqual( - dateRange.literalValue, - `RANGE ${INPUT_DATE_RANGE}` - ); - assert.strictEqual(dateRange.elementType, 'DATE'); - assert.deepStrictEqual(dateRange.value, { - start: '2020-01-01', - end: '2020-12-31', - }); - - const datetimeRange = bq.range({ - start: bq.datetime('2020-01-01 12:00:00'), - end: bq.datetime('2020-12-31 12:00:00'), - }); - assert.strictEqual(datetimeRange.apiValue, INPUT_DATETIME_RANGE); - assert.strictEqual( - datetimeRange.literalValue, - `RANGE ${INPUT_DATETIME_RANGE}` - ); - assert.strictEqual(datetimeRange.elementType, 'DATETIME'); - assert.deepStrictEqual(datetimeRange.value, { - start: '2020-01-01 12:00:00', - end: '2020-12-31 12:00:00', - }); - - const timestampRange = bq.range({ - start: bq.timestamp('2020-10-01 12:00:00+08'), - end: bq.timestamp('2020-12-31 12:00:00+08'), - }); - assert.strictEqual( - timestampRange.apiValue, - '[2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' - ); - assert.strictEqual( - timestampRange.literalValue, - 'RANGE [2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' - ); - assert.strictEqual(timestampRange.elementType, 'TIMESTAMP'); - assert.deepStrictEqual(timestampRange.value, { - start: '2020-10-01T04:00:00.000Z', - end: '2020-12-31T04:00:00.000Z', - }); - }); - - it('should accept a start/end as string with element type', () => { - const dateRange = bq.range( - { - start: '2020-01-01', - end: '2020-12-31', - }, - 'DATE' - ); - assert.strictEqual(dateRange.apiValue, INPUT_DATE_RANGE); - assert.strictEqual( - dateRange.literalValue, - `RANGE ${INPUT_DATE_RANGE}` - ); - assert.strictEqual(dateRange.elementType, 'DATE'); - - const datetimeRange = bq.range( - { - start: '2020-01-01 12:00:00', - end: '2020-12-31 12:00:00', - }, - 'DATETIME' - ); - assert.strictEqual(datetimeRange.apiValue, INPUT_DATETIME_RANGE); - assert.strictEqual( - datetimeRange.literalValue, - `RANGE ${INPUT_DATETIME_RANGE}` - ); - assert.strictEqual(datetimeRange.elementType, 'DATETIME'); - - const timestampRange = bq.range( - { - start: '2020-10-01 12:00:00+08', - end: '2020-12-31 12:00:00+08', - }, - 'TIMESTAMP' - ); - assert.strictEqual( - timestampRange.apiValue, - '[2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' - ); - assert.strictEqual( - timestampRange.literalValue, - 'RANGE [2020-10-01T04:00:00.000Z, 2020-12-31T04:00:00.000Z)' - ); - assert.strictEqual(timestampRange.elementType, 'TIMESTAMP'); - }); - - it('should accept a Range with start and/or end missing', () => { - const dateRange = bq.range( - { - start: '2020-01-01', - }, - 'DATE' - ); - assert.strictEqual( - dateRange.literalValue, - 'RANGE [2020-01-01, UNBOUNDED)' - ); - - const datetimeRange = bq.range( - { - end: '2020-12-31 12:00:00', - }, - 'DATETIME' - ); - assert.strictEqual( - datetimeRange.literalValue, - 'RANGE [UNBOUNDED, 2020-12-31 12:00:00)' - ); - - const timestampRange = bq.range({}, 'TIMESTAMP'); - assert.strictEqual( - timestampRange.literalValue, - 'RANGE [UNBOUNDED, UNBOUNDED)' - ); - }); - }); - describe('geography', () => { const INPUT_STRING = 'POINT(1 2)'; @@ -1428,15 +1216,6 @@ describe('BigQuery', () => { BigQuery.getTypeDescriptorFromValue_(bq.geography('POINT (1 1')).type, 'GEOGRAPHY' ); - assert.strictEqual( - BigQuery.getTypeDescriptorFromValue_( - bq.range( - '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', - 'TIMESTAMP' - ) - ).type, - 'RANGE' - ); }); it('should return correct type for an array', () => { @@ -1877,12 +1656,10 @@ describe('BigQuery', () => { const time = {type: 'TIME'}; const date = {type: 'DATE'}; const geo = {type: 'GEOGRAPHY'}; - const range = {type: 'RANGE'}; assert.strictEqual(BigQuery._isCustomType(time), true); assert.strictEqual(BigQuery._isCustomType(date), true); assert.strictEqual(BigQuery._isCustomType(geo), true); - assert.strictEqual(BigQuery._isCustomType(range), true); }); }); }); diff --git a/test/table.ts b/test/table.ts index 47d269f87..84d71ce5e 100644 --- a/test/table.ts +++ b/test/table.ts @@ -346,15 +346,6 @@ describe('BigQuery/Table', () => { const date = new Date(); assert.strictEqual(Table.encodeValue_(date), date.toJSON()); - - const range = BigQuery.range( - '[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)', - 'TIMESTAMP' - ); - assert.deepEqual(Table.encodeValue_(range), { - start: '2020-10-01T04:00:00.000Z', - end: '2020-12-31T04:00:00.000Z', - }); }); it('should properly encode custom types', () => { From 4eaa92d0a1e01e67f50c7035d1f323510284d910 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 12:43:53 -0400 Subject: [PATCH 4/4] chore(main): release 7.6.1 (#1356) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ package.json | 2 +- samples/package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4ea4630..e3adfec26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://www.npmjs.com/package/@google-cloud/bigquery?activeTab=versions +## [7.6.1](https://github.com/googleapis/nodejs-bigquery/compare/v7.6.0...v7.6.1) (2024-04-26) + + +### Bug Fixes + +* Should not override queryParameters if params not informed ([#1359](https://github.com/googleapis/nodejs-bigquery/issues/1359)) ([2b66d1e](https://github.com/googleapis/nodejs-bigquery/commit/2b66d1e5d57d754d188e8b45d8f58de0beff39b3)) + ## [7.6.0](https://github.com/googleapis/nodejs-bigquery/compare/v7.5.2...v7.6.0) (2024-04-09) diff --git a/package.json b/package.json index 4028d0faf..83405a30d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/bigquery", "description": "Google BigQuery Client Library for Node.js", - "version": "7.6.0", + "version": "7.6.1", "license": "Apache-2.0", "author": "Google LLC", "engines": { diff --git a/samples/package.json b/samples/package.json index 40a1ddc26..2ce6ac21a 100644 --- a/samples/package.json +++ b/samples/package.json @@ -17,7 +17,7 @@ "fix": "gts fix" }, "dependencies": { - "@google-cloud/bigquery": "^7.6.0", + "@google-cloud/bigquery": "^7.6.1", "@google-cloud/storage": "^7.0.0", "google-auth-library": "^9.6.0", "readline-promise": "^1.0.4",