diff --git a/src/data-type.ts b/src/data-type.ts index 77d81eed9..ae0e4447b 100644 --- a/src/data-type.ts +++ b/src/data-type.ts @@ -80,7 +80,7 @@ export interface DataType { generateTypeInfo(parameter: ParameterData, options: InternalConnectionOptions): Buffer; generateParameterLength(parameter: ParameterData, options: InternalConnectionOptions): Buffer; generateParameterData(parameter: ParameterData, options: InternalConnectionOptions): Generator; - validate(value: any, collation: Collation | undefined): any; // TODO: Refactor 'any' and replace with more specific type. + validate(value: any, collation: Collation | undefined, options?: InternalConnectionOptions): any; // TODO: Refactor 'any' and replace with more specific type. hasTableName?: boolean; diff --git a/src/data-types/datetime.ts b/src/data-types/datetime.ts index 02f62ef15..9c5699dc5 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -1,11 +1,18 @@ import { type DataType } from '../data-type'; import DateTimeN from './datetimen'; import { ChronoUnit, LocalDate } from '@js-joda/core'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1900, 1); const NULL_LENGTH = Buffer.from([0x00]); const DATA_LENGTH = Buffer.from([0x08]); +const MIN_DATE = new Date('January 1, 1753'); +const MAX_DATE = new Date('December 31, 9999'); + + const DateTime: DataType = { id: 0x3D, type: 'DATETIME', @@ -34,7 +41,7 @@ const DateTime: DataType = { const value = parameter.value as any; // Temporary solution. Remove 'any' later. - let date; + let date: LocalDate; if (options.useUTC) { date = LocalDate.of(value.getUTCFullYear(), value.getUTCMonth() + 1, value.getUTCDate()); } else { @@ -72,7 +79,7 @@ const DateTime: DataType = { }, // TODO: type 'any' needs to be revisited. - validate: function(value): null | number { + validate: function(value: any, collation: Collation | undefined, options: InternalConnectionOptions): null | number { if (value == null) { return null; } @@ -85,6 +92,18 @@ const DateTime: DataType = { throw new TypeError('Invalid date.'); } + value = value as Date; + + // TODO: check date range: January 1, 1753, through December 31, 9999 + // : time range: 00:00:00 through 23:59:59.997 + if (options && options.useUTC) { + value = new Date(value.toUTCString()); + } + + if (value < MIN_DATE || value > MAX_DATE) { + throw new TypeError('Out of range.'); + } + return value; } }; diff --git a/src/data-types/smalldatetime.ts b/src/data-types/smalldatetime.ts index b17d0d834..15338eb38 100644 --- a/src/data-types/smalldatetime.ts +++ b/src/data-types/smalldatetime.ts @@ -1,9 +1,13 @@ +import { type InternalConnectionOptions } from '../connection'; import { type DataType } from '../data-type'; import DateTimeN from './datetimen'; - +import { Collation } from '../collation'; const EPOCH_DATE = new Date(1900, 0, 1); const UTC_EPOCH_DATE = new Date(Date.UTC(1900, 0, 1)); +const MIN_DATE = new Date(1900, 1, 1); +const MAX_DATE = new Date(2079, 5, 6, 23, 59, 59, 0); + const DATA_LENGTH = Buffer.from([0x04]); const NULL_LENGTH = Buffer.from([0x00]); @@ -51,7 +55,7 @@ const SmallDateTime: DataType = { yield buffer; }, - validate: function(value): null | Date { + validate: function(value, collation: Collation | undefined, options: InternalConnectionOptions): null | Date { if (value == null) { return null; } @@ -64,6 +68,24 @@ const SmallDateTime: DataType = { throw new TypeError('Invalid date.'); } + value = value as Date; + + if (options && options.useUTC) { + value = new Date(value.toUTCString()); + } + + if (value < EPOCH_DATE) { + throw new TypeError('Out of range.'); + } + + if (value.getFullYear() < 1900) { + throw new TypeError('Out of range.'); + } + + if (value < MIN_DATE || value > MAX_DATE) { + throw new TypeError('Out of range.'); + } + return value; } }; diff --git a/test/unit/data-type.js b/test/unit/data-type.js index 6f51391bd..899a1264c 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -246,6 +246,20 @@ describe('DateTime', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('returns a TypeError for dates that are below January 1, 1753', function() { + assert.throws(() => { + TYPES.DateTime.validate(new Date('January 1, 1752')); + }, TypeError, 'Out of range.'); + }); + + it('returns a TypeError for dates that are greater than December 31, 9999', function() { + assert.throws(() => { + TYPES.DateTime.validate(new Date('December 31, 10000')); + }, TypeError, 'Out of range.'); + }); + }); }); describe('DateTime2', function() { @@ -829,7 +843,7 @@ describe('NVarChar', function() { describe('.generateTypeInfo', function() { it('returns the correct type information', function() { - // Length <= Maximum Length + // Length <= Maximum Length const type = TYPES.NVarChar; const expected = Buffer.from([0xE7, 2, 0, 0x00, 0x00, 0x00, 0x00, 0x00]); @@ -919,6 +933,32 @@ describe('SmallDateTime', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('throws Invalid Date error for NaN input', function() { + assert.throws(() => { + TYPES.SmallDateTime.validate('string'); + }, TypeError, 'Invalid date.'); + }); + + it('throws Out of Range error for dates out of range', function() { + assert.throws(() => { + TYPES.SmallDateTime.validate(new Date('January 1, 1899')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.SmallDateTime.validate(new Date('January 1, 2080')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.SmallDateTime.validate(new Date('July 1, 2079')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.SmallDateTime.validate(new Date('June 7, 2079')); + }, TypeError, 'Out of range.'); + }); + }); }); describe('SmallInt', function() { @@ -1152,7 +1192,7 @@ describe('TVP', function() { TYPES.TVP.generateParameterLength({ value: { columns: [{ name: 'user_id', type: TYPES.Int }], - rows: [[ 15 ], [ 16 ]] + rows: [[15], [16]] } }), Buffer.from([0x01, 0x00]) @@ -1164,7 +1204,7 @@ describe('TVP', function() { it('correctly converts TVP table values', function() { const value = { columns: [{ name: 'user_id', type: TYPES.Int }], - rows: [[ 15 ]] + rows: [[15]] }; const expected = Buffer.from('0000000000002604000001040f00000000', 'hex'); const parameterValue = { value };