From 1cf92ebf3c8603efe86506c61cdf749cb50cfd55 Mon Sep 17 00:00:00 2001 From: Ian Chok Date: Mon, 21 Jun 2021 16:53:23 -0700 Subject: [PATCH 1/5] fix: out-of-range date error handle --- examples/minimal.js | 37 +++++++++------------- src/data-type.ts | 2 +- src/data-types/datetime.ts | 21 +++++++++++-- src/data-types/smalldatetime.ts | 24 ++++++++++++++- test/unit/data-type.js | 54 +++++++++++++++++++++++++++++++-- 5 files changed, 109 insertions(+), 29 deletions(-) diff --git a/examples/minimal.js b/examples/minimal.js index 300dd7873..ef9419b83 100644 --- a/examples/minimal.js +++ b/examples/minimal.js @@ -1,19 +1,20 @@ -var Connection = require('../lib/tedious').Connection; -var Request = require('../lib/tedious').Request; +const { Connection, Request, TYPES } = require('../lib/tedious'); var config = { - server: '192.168.1.212', - authentication: { - type: 'default', - options: { - userName: 'test', - password: 'test' + "server": "localhost", + "authentication": { + "type": "default", + "options": { + "userName": "sa", + "password": "Password1" } }, - options: { - port: 1433 // Default Port + "options": { + "port": 1433, + "database": "master", + "trustServerCertificate": true } -}; +} const connection = new Connection(config); @@ -29,8 +30,9 @@ connection.on('connect', (err) => { connection.connect(); function executeStatement() { - const request = new Request('select * from MyTable', (err, rowCount) => { + const request = new Request('select CAST(@param as smalldatetime(max))', (err, rowCount) => { if (err) { + console.log('>>> ERROR CAUGHT') throw err; } @@ -38,16 +40,7 @@ function executeStatement() { connection.close(); }); - // Emits a 'DoneInProc' event when completed. - request.on('row', (columns) => { - columns.forEach((column) => { - if (column.value === null) { - console.log('NULL'); - } else { - console.log(column.value); - } - }); - }); + request.addParameter('param', TYPES.SmallDateTime, 'January 1, 1899 10:04:00') request.on('done', (rowCount) => { console.log('Done is called!'); diff --git a/src/data-type.ts b/src/data-type.ts index 12b69689d..d4eef7a9a 100644 --- a/src/data-type.ts +++ b/src/data-type.ts @@ -71,7 +71,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): any; // TODO: Refactor 'any' and replace with more specific type. + validate(value: any, 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 ee8fcd772..dc82fe015 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -1,11 +1,16 @@ import { DataType } from '../data-type'; import DateTimeN from './datetimen'; import { ChronoUnit, LocalDate } from '@js-joda/core'; +import { InternalConnectionOptions } from '../connection'; 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 +39,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 +77,7 @@ const DateTime: DataType = { }, // TODO: type 'any' needs to be revisited. - validate: function(value): null | number { + validate: function(value: any, options: InternalConnectionOptions): null | number { if (value == null) { return null; } @@ -85,6 +90,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 4b171d9c1..74390cb5b 100644 --- a/src/data-types/smalldatetime.ts +++ b/src/data-types/smalldatetime.ts @@ -1,9 +1,13 @@ +import { InternalConnectionOptions } from '../connection'; import { DataType } from '../data-type'; import DateTimeN from './datetimen'; 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, 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 6bd84c97e..deb6c3bcb 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -246,6 +246,28 @@ 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.'); + }); + + // it('returns a TypeError for dates that are below January 1, 1753', function() { + // console.log('UTC = ', new Date('1 Jan 1753 00:00:00 CET').toUTCString) + // assert.throws(() => { + // TYPES.DateTime.validate(new Date('1 Jan 1753 00:00:00 CET'), { useUTC: true }); + // }, TypeError, 'Out of range.'); + // }) + + }); }); describe('DateTime2', function() { @@ -829,7 +851,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 +941,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 +1200,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 +1212,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 }; From 5bbe8cbac93c580d44cafb0f7a8e1e308ba8b649 Mon Sep 17 00:00:00 2001 From: Ian Chok Date: Mon, 21 Jun 2021 17:01:04 -0700 Subject: [PATCH 2/5] chore: recover example file, remove comments --- examples/minimal.js | 37 ++++++++++++++++++++++--------------- test/unit/data-type.js | 8 -------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/examples/minimal.js b/examples/minimal.js index ef9419b83..300dd7873 100644 --- a/examples/minimal.js +++ b/examples/minimal.js @@ -1,20 +1,19 @@ -const { Connection, Request, TYPES } = require('../lib/tedious'); +var Connection = require('../lib/tedious').Connection; +var Request = require('../lib/tedious').Request; var config = { - "server": "localhost", - "authentication": { - "type": "default", - "options": { - "userName": "sa", - "password": "Password1" + server: '192.168.1.212', + authentication: { + type: 'default', + options: { + userName: 'test', + password: 'test' } }, - "options": { - "port": 1433, - "database": "master", - "trustServerCertificate": true + options: { + port: 1433 // Default Port } -} +}; const connection = new Connection(config); @@ -30,9 +29,8 @@ connection.on('connect', (err) => { connection.connect(); function executeStatement() { - const request = new Request('select CAST(@param as smalldatetime(max))', (err, rowCount) => { + const request = new Request('select * from MyTable', (err, rowCount) => { if (err) { - console.log('>>> ERROR CAUGHT') throw err; } @@ -40,7 +38,16 @@ function executeStatement() { connection.close(); }); - request.addParameter('param', TYPES.SmallDateTime, 'January 1, 1899 10:04:00') + // Emits a 'DoneInProc' event when completed. + request.on('row', (columns) => { + columns.forEach((column) => { + if (column.value === null) { + console.log('NULL'); + } else { + console.log(column.value); + } + }); + }); request.on('done', (rowCount) => { console.log('Done is called!'); diff --git a/test/unit/data-type.js b/test/unit/data-type.js index deb6c3bcb..1fab0187c 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -259,14 +259,6 @@ describe('DateTime', function() { TYPES.DateTime.validate(new Date('December 31, 10000')); }, TypeError, 'Out of range.'); }); - - // it('returns a TypeError for dates that are below January 1, 1753', function() { - // console.log('UTC = ', new Date('1 Jan 1753 00:00:00 CET').toUTCString) - // assert.throws(() => { - // TYPES.DateTime.validate(new Date('1 Jan 1753 00:00:00 CET'), { useUTC: true }); - // }, TypeError, 'Out of range.'); - // }) - }); }); From fbf3c746ff1df98896e4a9edf278257f31a5eedd Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:15:26 -0800 Subject: [PATCH 3/5] add collation to validate functions --- src/data-types/datetime.ts | 6 ++++-- src/data-types/smalldatetime.ts | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/data-types/datetime.ts b/src/data-types/datetime.ts index 38a1d9ee3..9c5699dc5 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -1,7 +1,9 @@ import { type DataType } from '../data-type'; import DateTimeN from './datetimen'; import { ChronoUnit, LocalDate } from '@js-joda/core'; -import { InternalConnectionOptions } from '../connection'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1900, 1); const NULL_LENGTH = Buffer.from([0x00]); @@ -77,7 +79,7 @@ const DateTime: DataType = { }, // TODO: type 'any' needs to be revisited. - validate: function(value: any, options: InternalConnectionOptions): null | number { + validate: function(value: any, collation: Collation | undefined, options: InternalConnectionOptions): null | number { if (value == null) { return null; } diff --git a/src/data-types/smalldatetime.ts b/src/data-types/smalldatetime.ts index cba06bbcc..15338eb38 100644 --- a/src/data-types/smalldatetime.ts +++ b/src/data-types/smalldatetime.ts @@ -1,7 +1,7 @@ -import { InternalConnectionOptions } from '../connection'; +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)); @@ -55,7 +55,7 @@ const SmallDateTime: DataType = { yield buffer; }, - validate: function(value, options: InternalConnectionOptions): null | Date { + validate: function(value, collation: Collation | undefined, options: InternalConnectionOptions): null | Date { if (value == null) { return null; } From 2349cb3b688d9cc69412ea416d2f12210e10f063 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:24:48 -0800 Subject: [PATCH 4/5] Update datetime error message --- src/data-types/datetime.ts | 2 +- test/unit/data-type.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data-types/datetime.ts b/src/data-types/datetime.ts index 9c5699dc5..b983af2b6 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -101,7 +101,7 @@ const DateTime: DataType = { } if (value < MIN_DATE || value > MAX_DATE) { - throw new TypeError('Out of range.'); + throw new TypeError('Date is out of range.'); } return value; diff --git a/test/unit/data-type.js b/test/unit/data-type.js index 899a1264c..1fe3a7123 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -251,13 +251,13 @@ describe('DateTime', 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.'); + }, TypeError, 'Date is 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.'); + }, TypeError, 'Date is out of range.'); }); }); }); From 3b6ec514bad27d4c4070a18666f8119bf006e381 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:46:17 -0800 Subject: [PATCH 5/5] Revert "Update datetime error message" This reverts commit 2349cb3b688d9cc69412ea416d2f12210e10f063. --- src/data-types/datetime.ts | 2 +- test/unit/data-type.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data-types/datetime.ts b/src/data-types/datetime.ts index b983af2b6..9c5699dc5 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -101,7 +101,7 @@ const DateTime: DataType = { } if (value < MIN_DATE || value > MAX_DATE) { - throw new TypeError('Date is out of range.'); + throw new TypeError('Out of range.'); } return value; diff --git a/test/unit/data-type.js b/test/unit/data-type.js index 1fe3a7123..899a1264c 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -251,13 +251,13 @@ describe('DateTime', 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, 'Date is out of range.'); + }, 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, 'Date is out of range.'); + }, TypeError, 'Out of range.'); }); }); });