From ae70fdc54d3b8d40fdb730800faa0c26c967af02 Mon Sep 17 00:00:00 2001 From: Ian McCurdy Date: Tue, 21 Mar 2023 17:01:40 -0400 Subject: [PATCH] INCOMPLETE DO NOT MERGE - Support for data format version 4 Added support for ALPHANUM, LONGDATE, SECONDDATE, DAYDATE, and SECONDTIME data types. --- lib/protocol/Connection.js | 24 ++-- lib/protocol/Parser.js | 2 +- lib/protocol/Reader.js | 114 ++++++++++++--- lib/protocol/Writer.js | 140 ++++++++++++++++++- lib/protocol/common/DataFormatVersion.js | 2 +- lib/protocol/common/NormalizedTypeCode.js | 2 +- lib/protocol/common/ReadFunction.js | 5 +- lib/protocol/part/ConnectOptions.js | 5 + lib/util/calendar.js | 76 +++++++++- lib/util/zeropad.js | 14 +- test/fixtures/parametersData.js | 88 +++++++++++- test/lib.Reader.js | 85 +++++++++--- test/lib.Stringifier.js | 2 +- test/lib.Writer.js | 162 ++++++++++++++++++++-- test/util.calendar.js | 38 ++++- 15 files changed, 672 insertions(+), 87 deletions(-) diff --git a/lib/protocol/Connection.js b/lib/protocol/Connection.js index 447c01f..8716b5b 100644 --- a/lib/protocol/Connection.js +++ b/lib/protocol/Connection.js @@ -457,15 +457,23 @@ Connection.prototype.connect = function connect(options, cb) { } delete options[key]; } + if(key.toUpperCase() === "DATAFORMATVERSION") { + this.connectOptions.setOptions([ + {name : common.ConnectOption.DATA_FORMAT_VERSION, + value : options[key]}, + {name : common.ConnectOption.DATA_FORMAT_VERSION2, + value : options[key]} + ]); + } } - this.connectOptions.setOptions([{ - name : common.ConnectOption.OS_USER, - value : this._clientInfo.getUser() - }]); - this.clientContextOptions.setOptions([{ - name : common.ClientContextOption.CLIENT_APPLICATION_PROGRAM, - value : this._clientInfo.getApplication() - }]); + this.connectOptions.setOptions([ + {name : common.ConnectOption.OS_USER, + value : this._clientInfo.getUser()} + ]); + this.clientContextOptions.setOptions([ + {name : common.ClientContextOption.CLIENT_APPLICATION_PROGRAM, + value : this._clientInfo.getApplication()} + ]); if(options["disableCloudRedirect"] == true) { this._redirectType = common.RedirectType.REDIRECTION_DISABLED; } diff --git a/lib/protocol/Parser.js b/lib/protocol/Parser.js index 3094a3f..336c524 100644 --- a/lib/protocol/Parser.js +++ b/lib/protocol/Parser.js @@ -245,4 +245,4 @@ function getNestedMetadata(metadata, options) { } metadata.forEach(pushTableColumn); return tables; -} \ No newline at end of file +} diff --git a/lib/protocol/Reader.js b/lib/protocol/Reader.js index 1dd165b..e25f8ee 100644 --- a/lib/protocol/Reader.js +++ b/lib/protocol/Reader.js @@ -15,16 +15,20 @@ var util = require('../util'); var bignum = util.bignum; +var lpad2 = util.lpad2; +var lpad4 = util.lpad4; +var lrpad9 = util.lrpad9; var common = require('./common'); var LobOptions = common.LobOptions; +var calendar = util.calendar; module.exports = Reader; -function Reader(buffer, lobFactory, scrictEncoding) { +function Reader(buffer, lobFactory, strictEncoding) { this.buffer = buffer; this.offset = 0; this.lobFactory = lobFactory; - this.scrictEncoding = scrictEncoding; + this.strictEncoding = strictEncoding; } Reader.prototype.hasMore = function hasMore() { @@ -75,7 +79,9 @@ Reader.prototype.readBinary = function readBinary() { return this.readBytes(); }; -Reader.prototype.readBytes = function readBytes(isString) { +Reader.prototype.readBytes = function readBytes(isString, isAlphanum) { + const ALPHANUM_LENGTH_MASK = 0x7f; + const ALPHANUM_NUMERIC_VALUE = 0x80; var length = this.buffer[this.offset++]; switch (length) { case 0xff: @@ -95,7 +101,21 @@ Reader.prototype.readBytes = function readBytes(isString) { if (isString) { value = util.convert.decode( this.buffer.slice(this.offset, this.offset + length), - this.scrictEncoding); + this.strictEncoding); + } else if (isAlphanum) { + var ind = this.buffer[this.offset]; + var newOffset = this.offset + 1; + var definitionLen = ind & ALPHANUM_LENGTH_MASK; + value = util.convert.decode( + this.buffer.slice(newOffset, newOffset + length - 1), + this.strictEncoding); + if (ind & ALPHANUM_NUMERIC_VALUE) { + // the value is purely numeric + if (definitionLen >= length) { + // zero-pad the value + value = "0".repeat(definitionLen - length + 1) + value; + } + } } else { value = new Buffer(length); this.buffer.copy(value, 0, this.offset, this.offset + length); @@ -123,8 +143,8 @@ Reader.prototype.readDate = function readDate() { high &= 0x3f; year |= high << 8; return util.lpad4(year) + '-' + - util.lpad2(month) + '-' + - util.lpad2(day); + lpad2(month) + '-' + + lpad2(day); }; Reader.prototype.readTime = function readTime() { @@ -142,9 +162,9 @@ Reader.prototype.readTime = function readTime() { // msb set ==> not null // unset msb hour &= 0x7f; - return util.lpad2(hour) + ':' + - util.lpad2(min) + ':' + - util.lpad2(msec / 1000); + return lpad2(hour) + ':' + + lpad2(min) + ':' + + lpad2(msec / 1000); }; Reader.prototype.readTimestamp = function readTimestamp() { @@ -163,12 +183,13 @@ Reader.prototype.readTimestamp = function readTimestamp() { }; Reader.prototype.readDayDate = function readDayDate() { - var value = this.buffer.readInt32LE(this.offset); + var value = this.buffer.readUInt32LE(this.offset); this.offset += 4; if (value === 3652062 || value === 0) { return null; } - return value - 1; + var date = calendar.DATE(value); + return lpad4(date.y) + "-" + lpad2(date.m) + "-" + lpad2(date.d); }; Reader.prototype.readSecondTime = function readSecondTime() { @@ -177,7 +198,13 @@ Reader.prototype.readSecondTime = function readSecondTime() { if (value === 86402 || value === 0) { return null; } - return value - 1; + value -= 1; + const seconds = value % 60; + value = ~~(value / 60); + const minutes = value % 60; + value = ~~(value / 60); + const hours = value; + return lpad2(hours) + ':' + lpad2(minutes) + ':' + lpad2(seconds); }; Reader.prototype.readSecondDate = function readSecondDate() { @@ -186,27 +213,64 @@ Reader.prototype.readSecondDate = function readSecondDate() { if (value === 315538070401 || value === 0) { return null; } - return value - 1; + const dayFactor = 60 * 60 * 24; + var seconds, minutes, hours, day, month, year; + value -= 1; + var timeValue = value % dayFactor; + var dayDate = ~~(value / dayFactor) + 1; + var date = calendar.DATE(dayDate); + year = date.y; + month = date.m; + day = date.d; + seconds = timeValue % 60; + timeValue = ~~(timeValue / 60); + minutes = timeValue % 60; + hours = ~~(timeValue / 60); + var dateString = lpad4(year) + '-' + lpad2(month) + '-' + lpad2(day) + var timeString = lpad2(hours) + ':' + lpad2(minutes) + ':' + lpad2(seconds); + return dateString + ' ' + timeString; }; Reader.prototype.readLongDate = function readLongDate() { var value = bignum.readInt64LE(this.buffer, this.offset); + var seconds, minutes, hours, day, month, year; + const dayFactor = 60 * 60 * 24; this.offset += 8; if (value === '3155380704000000001' || value === 0) { return null; } + var fractionalSeconds, secondDate; + if (typeof value === 'string') { - /* FIXME */ var index = value.length - 7; - var secondDate = parseInt(value.substring(0, index), 10); - var fractionalSeconds = parseInt(value.substring(index), 10) - 1; - if (fractionalSeconds < 0) { - fractionalSeconds = 9999999; - secondDate -= 1; - } - return secondDate + util.lpad7(fractionalSeconds); + secondDate = parseInt(value.substring(0, index), 10); + fractionalSeconds = parseInt(value.substring(index), 10); + } else { + secondDate = ~~(value / 10000000); + fractionalSeconds = value % 10000000; } - return value - 1; + + if (fractionalSeconds == 0) { + fractionalSeconds = 9999999; + secondDate -= 1; + } else { + fractionalSeconds -= 1; + } + var timeValue = secondDate % dayFactor; + var dayDate = ~~(secondDate / dayFactor) + 1; + seconds = timeValue % 60; + timeValue = ~~(timeValue / 60); + minutes = timeValue % 60; + timeValue = ~~(timeValue / 60); + hours = timeValue; + + var date = calendar.DATE(dayDate); + day = date.d; + month = date.m; + year = date.y; + var dateString = lpad4(year) + '-' + lpad2(month) + '-' + lpad2(day) + var timeString = lpad2(hours) + ':' + lpad2(minutes) + ':' + lpad2(seconds) + '.' + lrpad9(fractionalSeconds); + return dateString + ' ' + timeString; }; Reader.prototype.readBLob = function readBLob() { @@ -301,6 +365,10 @@ Reader.prototype.readDecimal = function readDecimal(fraction) { return value; }; +Reader.prototype.readAlphanum = function readAlphanum() { + return this.readBytes(false, true); +} + Reader.LobDescriptor = LobDescriptor; function LobDescriptor(type, options, charLength, byteLength, locatorId, chunk, defaultType) { @@ -320,4 +388,4 @@ Object.defineProperties(LobDescriptor.prototype, { return !!(this.options & LobOptions.LAST_DATA); } } -}); \ No newline at end of file +}); diff --git a/lib/protocol/Writer.js b/lib/protocol/Writer.js index 78d4b03..9b6fc3c 100644 --- a/lib/protocol/Writer.js +++ b/lib/protocol/Writer.js @@ -20,6 +20,11 @@ var TypeCode = common.TypeCode; var LobOptions = common.LobOptions; var NormalizedTypeCode = common.NormalizedTypeCode; var bignum = util.bignum; +var calendar = util.calendar; +var isValidDay = calendar.isValidDay; +var isValidTime = calendar.isValidTime; +var isZeroDay = calendar.isZeroDay; +var isZeroTime = calendar.isZeroTime; var WRITE_LOB_REQUEST_HEADER_LENGTH = 21; exports = module.exports = Writer; @@ -380,7 +385,22 @@ Writer.prototype.push = function push(buffer) { Writer.prototype.pushNull = function pushNull(type) { /* jshint bitwise:false */ - var buffer = new Buffer([NormalizedTypeCode[type] | 0x80]); + var nullTypeCode; + switch(type) { + case TypeCode.LONGDATE: + case TypeCode.SECONDDATE: + nullTypeCode = TypeCode.TIMESTAMP | 0x80; + break; + case TypeCode.DAYDATE: + nullTypeCode = TypeCode.DATE | 0x80; + break; + case TypeCode.SECONDTIME: + nullTypeCode = TypeCode.TIME | 0x80; + break; + default: + nullTypeCode = NormalizedTypeCode[type] | 0x80; + } + var buffer = new Buffer([nullTypeCode]); this.push(buffer); }; @@ -606,22 +626,132 @@ Writer.prototype[TypeCode.TIMESTAMP] = function writeTimestamp(value) { Writer.prototype[TypeCode.DAYDATE] = function writeDayDate(value) { /* jshint unused:false */ - throw createNotImplementedError(); + var year, month, day; + if (util.isString(value)) { + var date = value.match(REGEX.DATE); + if (!date) { + throw createInputError('DATE'); + } + year = ~~date[1]; + month = ~~date[2]; + day = ~~date[3]; + } else { + throw createInputError('DATE'); + } + if(isZeroDay(day, month, year)) { + var buffer = new Buffer(5); + buffer[0] = TypeCode.DAYDATE; + buffer.writeUInt32LE(0, 1); + this.push(buffer); + return; + } + if(!isValidDay(day, month, year)) { + throw createInputError('DAYDATE'); + } + const dayDate = calendar.DAYDATE(year, month, day); + var buffer = new Buffer(5); + buffer[0] = TypeCode.DAYDATE; + buffer.writeUInt32LE(dayDate, 1); + this.push(buffer); }; Writer.prototype[TypeCode.SECONDTIME] = function writeSecondTime(value) { /* jshint unused:false */ - throw createNotImplementedError(); + var hours, minutes, seconds; + if (util.isString(value)) { + var ts = value.match(REGEX.TIME); + if(!ts) { + throw createInputError('SECONDTIME'); + } + hours = ~~ts[1]; + minutes = ~~ts[2]; + seconds = ~~ts[3]; + } else { + throw createInputError('SECONDTIME'); + } + if(!isValidTime(seconds, minutes, hours)) { + throw createInputError('SECONDTIME'); + } + const timeValue = ((hours * 60) + minutes) * 60 + seconds; + var buffer = new Buffer(5); + buffer[0] = TypeCode.SECONDTIME; + buffer.writeUInt32LE(timeValue + 1, 1); + this.push(buffer); }; Writer.prototype[TypeCode.LONGDATE] = function writeLongDate(value) { /* jshint unused:false */ - throw createNotImplementedError(); + var year, month, day, hours, minutes, seconds, nanoseconds; + if (util.isString(value)) { + var ts = value.match(REGEX.TIMESTAMP); + if (!ts) { + throw createInputError('LONGDATE'); + } + year = ~~ts[1]; + month = ~~ts[2]; + day = ~~ts[3]; + hours = ~~ts[4]; + minutes = ~~ts[5]; + seconds = ~~ts[6]; + nanoseconds = ~~((ts[6] * 1000000000) % 1000000000); + } else { + throw createInputError('LONGDATE'); + } + if(isZeroDay(day, month, year) && isZeroTime(seconds, minutes, hours) && nanoseconds === 0) { + var buffer = new Buffer(9); + buffer[0] = TypeCode.LONGDATE; + bignum.writeUInt64LE(buffer, 0, 1); + this.push(buffer); + return; + } + if(!isValidDay(day, month, year) || !isValidTime(seconds, minutes, hours)) { + throw createInputError('LONGDATE'); + } + const dayDate = calendar.DAYDATE(year, month, day); + const dayFactor = BigInt(10000000) * BigInt(60 * 60 * 24); + const timeValue = BigInt(((hours * 60) + minutes) * 60 + seconds) * BigInt(10000000) + BigInt(~~(nanoseconds / 100)); + const longDate = BigInt(dayDate - 1) * dayFactor + timeValue + BigInt(1); + var buffer = new Buffer(9); + buffer[0] = TypeCode.LONGDATE; + bignum.writeUInt64LE(buffer, String(longDate), 1); + this.push(buffer); }; Writer.prototype[TypeCode.SECONDDATE] = function writeSecondDate(value) { /* jshint unused:false */ - throw createNotImplementedError(); + var year, month, day, hours, minutes, seconds; + if (util.isString(value)) { + var ts = value.match(REGEX.TIMESTAMP); + if (!ts) { + throw createInputError('SECONDDATE'); + } + year = ~~ts[1]; + month = ~~ts[2]; + day = ~~ts[3]; + hours = ~~ts[4]; + minutes = ~~ts[5]; + seconds = ~~ts[6]; + } else { + throw createInputError('SECONDDATE'); + } + if(isZeroDay(day, month, year) && isZeroTime(seconds, minutes, hours)) { + var buffer = new Buffer(9); + buffer[0] = TypeCode.SECONDDATE; + bignum.writeUInt64LE(buffer, 0, 1); + this.push(buffer); + return; + } + if(!isValidDay(day, month, year) || !isValidTime(seconds, minutes, hours)) { + throw createInputError('SECONDDATE'); + } + const dayDate = calendar.DAYDATE(year, month, day); + const dayFactor = 60 * 60 * 24; + const timeValue = ((hours * 60) + minutes) * 60 + seconds; + const seconddate = BigInt(dayDate - 1) * BigInt(dayFactor) + BigInt(timeValue + 1); + var buffer = new Buffer(9); + buffer[0] = TypeCode.SECONDDATE; + bignum.writeUInt64LE(buffer, String(seconddate), 1) + this.push(buffer); }; function setChar(str, i, c) { diff --git a/lib/protocol/common/DataFormatVersion.js b/lib/protocol/common/DataFormatVersion.js index 02d2f4c..82a860e 100644 --- a/lib/protocol/common/DataFormatVersion.js +++ b/lib/protocol/common/DataFormatVersion.js @@ -19,4 +19,4 @@ module.exports = { // deprecated EXTENDED_DATE_TIME_SUPPORT: 3, LEVEL4: 4 -}; \ No newline at end of file +}; diff --git a/lib/protocol/common/NormalizedTypeCode.js b/lib/protocol/common/NormalizedTypeCode.js index 8bf6b9e..0f689df 100644 --- a/lib/protocol/common/NormalizedTypeCode.js +++ b/lib/protocol/common/NormalizedTypeCode.js @@ -67,4 +67,4 @@ NormalizedTypeCode[TypeCode.SECONDTIME] = TypeCode.SECONDTIME; // LongDate NormalizedTypeCode[TypeCode.LONGDATE] = TypeCode.LONGDATE; // SecondDate -NormalizedTypeCode[TypeCode.SECONDDATE] = TypeCode.SECONDDATE; \ No newline at end of file +NormalizedTypeCode[TypeCode.SECONDDATE] = TypeCode.SECONDDATE; diff --git a/lib/protocol/common/ReadFunction.js b/lib/protocol/common/ReadFunction.js index 6428369..d7158b5 100644 --- a/lib/protocol/common/ReadFunction.js +++ b/lib/protocol/common/ReadFunction.js @@ -36,6 +36,7 @@ var READ_NCLOB = 'readNCLob'; var READ_DOUBLE = 'readDouble'; var READ_FLOAT = 'readFloat'; var READ_DECIMAL = 'readDecimal'; +var READ_ALPHANUM = 'readAlphanum'; ReadFunction[TypeCode.TINYINT] = READ_TINYINT; ReadFunction[TypeCode.SMALLINT] = READ_SMALLINT; @@ -49,7 +50,7 @@ ReadFunction[TypeCode.NCHAR] = READ_STRING; ReadFunction[TypeCode.NVARCHAR] = READ_STRING; ReadFunction[TypeCode.NSTRING] = READ_STRING; ReadFunction[TypeCode.SHORTTEXT] = READ_STRING; -ReadFunction[TypeCode.ALPHANUM] = READ_STRING; +ReadFunction[TypeCode.ALPHANUM] = READ_ALPHANUM; ReadFunction[TypeCode.BINARY] = READ_BINARY; ReadFunction[TypeCode.VARBINARY] = READ_BINARY; ReadFunction[TypeCode.BSTRING] = READ_BINARY; @@ -68,4 +69,4 @@ ReadFunction[TypeCode.NLOCATOR] = READ_NCLOB; ReadFunction[TypeCode.TEXT] = READ_NCLOB; ReadFunction[TypeCode.DOUBLE] = READ_DOUBLE; ReadFunction[TypeCode.REAL] = READ_FLOAT; -ReadFunction[TypeCode.DECIMAL] = READ_DECIMAL; \ No newline at end of file +ReadFunction[TypeCode.DECIMAL] = READ_DECIMAL; diff --git a/lib/protocol/part/ConnectOptions.js b/lib/protocol/part/ConnectOptions.js index 7536176..d9015a9 100644 --- a/lib/protocol/part/ConnectOptions.js +++ b/lib/protocol/part/ConnectOptions.js @@ -111,8 +111,13 @@ function ConnectOptions() { // * `4` Baseline data type support (SAP HANA SPS 06) // Support for ALPHANUM, TEXT, SHORTTEXT, LONGDATE, SECONDDATE, // DAYDATE, and SECONDTIME. + // TODO: Once implementation and testing for data format version 4 are + // complete, replace dataFormatVersion and dataFormatVersion2 with the + // commented-out lines. this.dataFormatVersion = DataFormatVersion.COMPLETE_DATATYPE_SUPPORT; + //this.dataFormatVersion = DataFormatVersion.LEVEL4; this.dataFormatVersion2 = DataFormatVersion.COMPLETE_DATATYPE_SUPPORT; + //this.dataFormatVersion2 = DataFormatVersion.LEVEL4; } ConnectOptions.prototype.PROPERTY_NAMES = common.ConnectOptionName; diff --git a/lib/util/calendar.js b/lib/util/calendar.js index d1b0f73..30fa10e 100644 --- a/lib/util/calendar.js +++ b/lib/util/calendar.js @@ -21,7 +21,7 @@ var lpad2 = zeropad.lpad2; var TURN_OF_ERAS = 1721424; var GREGORIAN = 2299161; -exports.DAYDATE = function DAYDATE(year, month, day) { +/*exports.DAYDATE = function DAYDATE(year, month, day) { var A, B, C, E, F, Z; if (!month) { @@ -57,6 +57,46 @@ exports.DAYDATE = function DAYDATE(year, month, day) { F = ~~ (30.6001 * (month + 1)); Z = C + day + E + F - 1524; return Z + 1 - TURN_OF_ERAS; +};*/ + +function mustConvertToGregorian(year, month, day) { + const IGREG = (15 + 31 * (10 + 12 * 1582)); + return day + 31 * (month + 12 * year) >= IGREG; +} + +exports.DAYDATE = function DAYDATE(year, month, day) { + if (!month) { + var date = year; + if (typeof date === 'string') { + month = +date.substring(5, 7); + day = +date.substring(8, 10); + year = +date.substring(0, 4); + } else if (typeof date === 'object') { + month = date.getUTCMonth() + 1; + day = year.getUTCDate(); + year = year.getUTCFullYear(); + } + } + + var julienMonth = month; + var julienYear = year; + + if (julienMonth > 2) { + julienMonth += 1; + } else { + julienYear -= 1; + julienMonth += 13; + } + + var julienDayNumber = ~~ (365.25 * julienYear) + ~~ (30.6001 * julienMonth) + day + 1720995; + + if (mustConvertToGregorian(year, month, day)) { + var a = ~~ (julienYear / 100); + var b = ~~ (a / 4); + julienDayNumber += 2 - a + b; + } + + return julienDayNumber - TURN_OF_ERAS + 1; }; exports.DATE = function DATE(daydate) { @@ -85,5 +125,35 @@ exports.DATE = function DATE(daydate) { } else { year = C - 4716; } - return lpad4(year) + '-' + lpad2(month) + '-' + lpad2(day); -}; \ No newline at end of file + return {y: year, m: month, d: day}; +}; + +exports.isZeroDay = function isZeroDay(day, month, year) { + return (year === 0 && month === 0 && day === 0); +} + +exports.isZeroTime = function isZeroTime(seconds, minutes, hours) { + return (hours === 0 && minutes === 0 && seconds === 0); +} + +exports.isValidDay = function isValidDay(day, month, year) { + if( (year < 1) || (year > 9999) || (month < 1) || (month > 12) + || (day < 1) || (day > 31)) { + return false; + } + const daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if(day <= daysInMonth[month]) { + return true; + } else if( ((~~(year % 4) == 0) && ~~(year % 100)) + || (~~(year % 400) == 0)) { + if(month == 2 && day == 29) { + return true; + } + } + return false; +} + +exports.isValidTime = function isValidTime(seconds, minutes, hours) { + return ((hours >= 0) && (hours < 24) && (minutes >= 0) && (minutes < 60) + && (seconds >= 0) && (seconds < 60)); +} diff --git a/lib/util/zeropad.js b/lib/util/zeropad.js index c6c15e0..9cee27a 100644 --- a/lib/util/zeropad.js +++ b/lib/util/zeropad.js @@ -95,4 +95,16 @@ exports.lpad4 = function lpad4(n) { exports.lpad2 = function lpad2(n) { if (n >= INT_10_1) return ZERO_0 + n; return ZERO_1 + n; -}; \ No newline at end of file +}; + +exports.lrpad9 = function lrpad9(n) { + if (n >= INT_10_8) return n + ZERO_0; + if (n >= INT_10_7) return n + ZERO_1; + if (n >= INT_10_6) return n + ZERO_2; + if (n >= INT_10_5) return ZERO_1 + n + ZERO_2; + if (n >= INT_10_4) return ZERO_2 + n + ZERO_2; + if (n >= INT_10_3) return ZERO_3 + n + ZERO_2; + if (n >= INT_10_2) return ZERO_4 + n + ZERO_2; + if (n >= INT_10_1) return ZERO_5 + n + ZERO_2; + return ZERO_6 + n + ZERO_2; +}; diff --git a/test/fixtures/parametersData.js b/test/fixtures/parametersData.js index 3d3ed9f..aea5316 100644 --- a/test/fixtures/parametersData.js +++ b/test/fixtures/parametersData.js @@ -101,10 +101,14 @@ exports.ALL_TYPES = { '060ad7b941' + '0685ebc141' + '8c' + - // offset 167 - '1b06c8000000c6000000' + - '19060b0000008e010000' + - '1a060b00000099010000', + '3F38450B00' + + '3E6DB3C0DB0E000000' + + '40EDB00000' + + '3DBFB3AF519B36DB08' + + // offset 195 + '1b06c8000000e2000000' + + '19060b000000aa010000' + + '1a060b000000b5010000', 'hex'), blob, clob, nclob]) }, types: [ @@ -133,6 +137,10 @@ exports.ALL_TYPES = { TypeCode.REAL, TypeCode.REAL, TypeCode.BINARY, + TypeCode.DAYDATE, + TypeCode.SECONDDATE, + TypeCode.SECONDTIME, + TypeCode.LONGDATE, TypeCode.BLOB, TypeCode.CLOB, TypeCode.NCLOB @@ -163,6 +171,10 @@ exports.ALL_TYPES = { 23.23, 24.24, null, + '2023-04-04', + '2023-04-04 12:34:52', + '12:34:52', + '2023-04-04 12:34:52.1357246', blob, clob, nclob @@ -416,3 +428,71 @@ exports.DECIMAL = { "1234567890123456789012345678909999999" ] }; + +exports.DATETIME = { + part: { + argumentCount: 1, + buffer: new Buffer( + '90' + + '3dc9f830e43673b308' + + '3d027700111f64ed06' + + '3d811658b7c86aed06' + + '3d0100000000000000' + + '3d00c00a49082aca2b' + + '90' + + '3eb9830a990e000000' + + '3e0100000000000000' + + '3e80db887749000000' + + '8e' + + '3f9e120b00' + + '3f439d0600' + + '3f01000000' + + '3fddb93700' + + '8f' + + '4039880000' + + '4080510100' + + '4001000000', 'hex') + }, + types: [ + TypeCode.LONGDATE, + TypeCode.LONGDATE, + TypeCode.LONGDATE, + TypeCode.LONGDATE, + TypeCode.LONGDATE, + TypeCode.LONGDATE, + TypeCode.SECONDDATE, + TypeCode.SECONDDATE, + TypeCode.SECONDDATE, + TypeCode.SECONDDATE, + TypeCode.DAYDATE, + TypeCode.DAYDATE, + TypeCode.DAYDATE, + TypeCode.DAYDATE, + TypeCode.DAYDATE, + TypeCode.SECONDTIME, + TypeCode.SECONDTIME, + TypeCode.SECONDTIME, + TypeCode.SECONDTIME, + ], + values: [ + null, + '1987-10-16 09:41:12.84738', + '1582-10-15 12:30:30.0000001', + '1582-10-14 00:00:01', + '0001-01-01 00:00:00', + '9999-12-31 23:59:59.999999999999', + null, + '1987-10-16 09:41:12', + '0001-01-01 00:00:00', + '9999-12-31 23:59:59', + null, + '1987-10-16', + '1187-10-16', + '0001-01-01', + '9999-12-31', + null, + '09:41:12', + '23:59:59', + '00:00:00' + ] +}; diff --git a/test/lib.Reader.js b/test/lib.Reader.js index a3be99a..4e3eb35 100644 --- a/test/lib.Reader.js +++ b/test/lib.Reader.js @@ -284,12 +284,19 @@ describe('Lib', function () { var buffer = new Buffer([ 0x00, 0x00, 0x00, 0x00, 0xde, 0xb9, 0x37, 0x00, - 0x02, 0x00, 0x00, 0x00 + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x31, 0x45, 0x0B, 0x00, + 0xDD, 0xB9, 0x37, 0x00 + ]); var reader = new lib.Reader(buffer); should(reader.readDayDate() === null).ok; should(reader.readDayDate() === null).ok; - reader.readDayDate().should.equal(1); + reader.readDayDate().should.equal('0001-01-01'); + reader.readDayDate().should.equal('0001-01-02'); + reader.readDayDate().should.equal('2023-03-28'); + reader.readDayDate().should.equal('9999-12-31'); reader.hasMore().should.equal(false); }); @@ -297,41 +304,79 @@ describe('Lib', function () { var buffer = new Buffer([ 0x00, 0x00, 0x00, 0x00, 0x82, 0x51, 0x01, 0x00, - 0x02, 0x00, 0x00, 0x00 + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x8A, 0xA1, 0x00, 0x00, + 0x80, 0x51, 0x01, 0x00, ]); var reader = new lib.Reader(buffer); should(reader.readSecondTime() === null).ok; should(reader.readSecondTime() === null).ok; - reader.readSecondTime().should.equal(1); + reader.readSecondTime().should.equal('00:00:00'); + reader.readSecondTime().should.equal('00:00:01'); + reader.readSecondTime().should.equal('11:29:13'); + reader.readSecondTime().should.equal('23:59:59'); reader.hasMore().should.equal(false); }); it('should read a SecondDate', function () { - var buffer = new Buffer(24); - buffer.fill(0x00, 0, 8); - bignum.writeInt64LE(buffer, 315538070401, 8); - bignum.writeInt64LE(buffer, 2, 16); + //var buffer = new Buffer(24); + //buffer.fill(0x00, 0, 8); + //bignum.writeInt64LE(buffer, 315538070401, 8); + //bignum.writeInt64LE(buffer, 2, 16); + var buffer = new Buffer([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0xD8, 0x88, 0x77, 0x49, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x51, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x86, 0xB6, 0xB7, 0xDB, 0x0E, 0x00, 0x00, 0x00, + 0x80, 0xDB, 0x88, 0x77, 0x49, 0x00, 0x00, 0x00, + ]); var reader = new lib.Reader(buffer); should(reader.readSecondDate() === null).ok; should(reader.readSecondDate() === null).ok; - reader.readSecondDate().should.equal(1); + reader.readSecondDate().should.equal('0001-01-01 00:00:00'); + reader.readSecondDate().should.equal('0001-01-01 00:00:01'); + reader.readSecondDate().should.equal('0001-01-02 00:00:00'); + reader.readSecondDate().should.equal('2023-03-28 16:57:41'); + reader.readSecondDate().should.equal('9999-12-31 23:59:59'); reader.hasMore().should.equal(false); }); - it('should read a LongDate', function () { - var buffer = new Buffer(40); - buffer.fill(0x00, 0, 8); - bignum.writeInt64LE(buffer, '3155380704000000001', 8); - bignum.writeInt64LE(buffer, 2, 16); - bignum.writeInt64LE(buffer, '1000000000000000001', 24); - bignum.writeInt64LE(buffer, '1000000000000000000', 32); + var buffer = new Buffer([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xC0, 0x0A, 0x49, 0x08, 0x2A, 0xCA, 0x2B, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x39, 0x92, 0xE7, 0x9E, 0xDB, 0xA7, 0xD7, 0x08, + 0x00, 0xC0, 0x0A, 0x49, 0x08, 0x2A, 0xCA, 0x2B, + ]); var reader = new lib.Reader(buffer); (reader.readLongDate() === null).should.be.ok; (reader.readLongDate() === null).should.be.ok; - reader.readLongDate().should.equal(1); - reader.readLongDate().should.equal('1000000000000000000'); - reader.readLongDate().should.equal('999999999999999999'); + reader.readLongDate().should.equal('0001-01-01 00:00:00.000000000'); + reader.readLongDate().should.equal('0001-01-01 00:00:00.000000100'); + reader.readLongDate().should.equal('0001-01-01 00:00:01.000000000'); + reader.readLongDate().should.equal('2020-01-31 12:30:00.186732000'); + reader.readLongDate().should.equal('9999-12-31 23:59:59.999999900'); + reader.hasMore().should.equal(false); + }); + + it('should read an Alphanum', function () { + var buffer = new Buffer([ + 0xFF, + 0x07, 0x06, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33, + 0x04, 0x86, 0x31, 0x32, 0x33, + 0x07, 0x86, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 + ]); + var reader = new lib.Reader(buffer); + (reader.readAlphanum() === null).should.be.ok; + reader.readAlphanum().should.equal('abc123'); + reader.readAlphanum().should.equal('000123'); + reader.readAlphanum().should.equal('123456'); reader.hasMore().should.equal(false); }); }); @@ -375,4 +420,4 @@ describe('Lib', function () { lob.defaultType.should.eql(LobSourceType.NCLOB); }); -}); \ No newline at end of file +}); diff --git a/test/lib.Stringifier.js b/test/lib.Stringifier.js index 61ee939..132eab3 100644 --- a/test/lib.Stringifier.js +++ b/test/lib.Stringifier.js @@ -85,4 +85,4 @@ function testStringifier(chunks, rows, done) { stringifier.write(chunk); }); stringifier.end(); -} \ No newline at end of file +} diff --git a/test/lib.Writer.js b/test/lib.Writer.js index 310549d..821f09d 100644 --- a/test/lib.Writer.js +++ b/test/lib.Writer.js @@ -75,6 +75,18 @@ describe('Lib', function () { }); }); + it('should write datetime types', function (done) { + var test = data.DATETIME; + var writer = Writer.create(test); + writer.getParameters(SIZE, function (err, buffer) { + if (err) { + return done(err); + } + buffer.should.eql(test.part.buffer); + done(); + }); + }); + it('should write a BLOB', function (done) { var test = data.LOGO; var writer = Writer.create(test); @@ -267,6 +279,11 @@ describe('Lib', function () { writer._buffers[0][1].should.equal(lib.common.DATA_LENGTH_4BYTE_LENGTH_INDICATOR); }); + it('should set a LONGDATE value', function () { + var writer = new Writer([TypeCode.LONGDATE]); + + }); + it('should get Parameters where buffer excatly fits', function ( done) { var writer = new Writer([TypeCode.BLOB]); @@ -425,24 +442,149 @@ describe('Lib', function () { .throw(); }); - it('should raise not implemented error for DAYDATE', function () { - var writer = new Writer([TypeCode.DAYDATE]); - Writer.prototype.setValues.bind(writer, [1]).should.throw(); + it('should raise wrong input type error for LONGDATE', function () { + var writer = new Writer([TypeCode.LONGDATE]); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + // Regex does not match + Writer.prototype.setValues.bind(writer, [ + '2014-08-21|14:02:34' + ]).should + .throw(); + // Invalid year + Writer.prototype.setValues.bind(writer, [ + '0000-08-21 14:02:34.1234567' + ]).should + .throw(); + // Invalid Month + Writer.prototype.setValues.bind(writer, [ + '2014-13-21 14:02:34.1234567' + ]).should + .throw(); + // Invalid Day + Writer.prototype.setValues.bind(writer, [ + '2014-08-32 14:02:34.1234567' + ]).should + .throw(); + // Invalid Hour + Writer.prototype.setValues.bind(writer, [ + '2014-08-21 24:02:34.1234567' + ]).should + .throw(); + // Invalid Minute + Writer.prototype.setValues.bind(writer, [ + '2014-08-21 14:60:34.1234567' + ]).should + .throw(); + // Invalid Second + Writer.prototype.setValues.bind(writer, [ + '2014-08-21 14:02:60.1234567' + ]).should + .throw(); }); - it('should raise not implemented error for SECONDDATE', function () { + it('should raise wrong input type error for SECONDDATE', function () { var writer = new Writer([TypeCode.SECONDDATE]); - Writer.prototype.setValues.bind(writer, [1]).should.throw(); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + // Regex does not match + Writer.prototype.setValues.bind(writer, [ + '2014-08-21|14:02:34' + ]).should + .throw(); + // Invalid year + Writer.prototype.setValues.bind(writer, [ + '0000-08-21 14:02:34' + ]).should + .throw(); + // Invalid Month + Writer.prototype.setValues.bind(writer, [ + '2014-00-21 14:02:34' + ]).should + .throw(); + // Invalid Day + Writer.prototype.setValues.bind(writer, [ + '2014-08-00 14:02:34' + ]).should + .throw(); + // Invalid Hour + Writer.prototype.setValues.bind(writer, [ + '2014-08-21 90:02:34' + ]).should + .throw(); + // Invalid Minute + Writer.prototype.setValues.bind(writer, [ + '2014-08-21 14:90:34' + ]).should + .throw(); + // Invalid Second + Writer.prototype.setValues.bind(writer, [ + '2014-08-21 14:02:90' + ]).should + .throw(); }); - it('should raise not implemented error for LONGDATE', function () { - var writer = new Writer([TypeCode.LONGDATE]); - Writer.prototype.setValues.bind(writer, [1]).should.throw(); + it('should raise wrong input type error for DAYDATE', function () { + var writer = new Writer([TypeCode.DAYDATE]); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + // Regex does not match + Writer.prototype.setValues.bind(writer, [ + '2014|08|21' + ]).should + .throw(); + // Invalid year + Writer.prototype.setValues.bind(writer, [ + '198-08-21' + ]).should + .throw(); + // Invalid Month + Writer.prototype.setValues.bind(writer, [ + '2014-1-21 14:02:34' + ]).should + .throw(); + // Invalid Day + Writer.prototype.setValues.bind(writer, [ + '2014-08-1' + ]).should + .throw(); }); - it('should raise not implemented error for SECONDTIME', function () { + it('should raise wrong input type error for SECONDTIME', function () { var writer = new Writer([TypeCode.SECONDTIME]); - Writer.prototype.setValues.bind(writer, [1]).should.throw(); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + // Regex does not match + Writer.prototype.setValues.bind(writer, [ + '14-02-34' + ]).should + .throw(); + // Invalid Hour + Writer.prototype.setValues.bind(writer, [ + '1:02:34' + ]).should + .throw(); + // Invalid Minute + Writer.prototype.setValues.bind(writer, [ + '14:61:34' + ]).should + .throw(); + // Invalid Second + Writer.prototype.setValues.bind(writer, [ + '14:02:61' + ]).should + .throw(); + }); + + it('should raise wrong input type error for ALPHANUM', function () { + var writer = new Writer([TypeCode.ALPHANUM]); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + }); + + it('should raise wrong input type error for TEXT', function () { + var writer = new Writer([TypeCode.TEXT]); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + }); + + it('should raise wrong input type error for SHORTTEXT', function () { + var writer = new Writer([TypeCode.SHORTTEXT]); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); }); }); diff --git a/test/util.calendar.js b/test/util.calendar.js index 9ed3ffd..f381fa0 100644 --- a/test/util.calendar.js +++ b/test/util.calendar.js @@ -20,11 +20,11 @@ describe('Util', function () { describe('#DATE', function () { - it('should convert date to daydate', function () { - calendar.DATE(735284).should.equal('2014-02-18'); - calendar.DATE(577738).should.equal('1582-10-15'); - calendar.DATE(577737).should.equal('1582-10-04'); - calendar.DATE(1).should.equal('0001-01-01'); + it('should convert daydate to date', function () { + calendar.DATE(735284).should.deepEqual({y: 2014, m: 2, d: 18}); + calendar.DATE(577738).should.deepEqual({y: 1582, m: 10, d: 15}); + calendar.DATE(577737).should.deepEqual({y: 1582, m: 10, d: 4}); + calendar.DATE(1).should.deepEqual({y: 1, m: 1, d: 1}); }); }); @@ -43,9 +43,33 @@ describe('Util', function () { }); it('should convert year, month and day values to daydate', function () { - calendar.DAYDATE(1582, 9, 5).should.equal(577738); + calendar.DAYDATE(1582, 10, 5).should.equal(577738); }); }); -}); \ No newline at end of file + describe('#DATETIMEVALIDITY', function () { + + it('should identify invalid days', function() { + calendar.isValidDay(20, 11, 1995).should.equal(true); + calendar.isValidDay(31, 11, 1995).should.equal(false); + calendar.isValidDay(20, 13, 1995).should.equal(false); + calendar.isValidDay(20, 11, 10000).should.equal(false); + }); + + it('should identify invalid times', function() { + calendar.isValidTime(59, 59, 23).should.equal(true); + calendar.isValidTime(60, 59, 23).should.equal(false); + calendar.isValidTime(59, 60, 23).should.equal(false); + calendar.isValidTime(59, 59, 24).should.equal(false); + }); + + it('should identify leap years', function() { + calendar.isValidDay(29, 2, 2023).should.equal(false); + calendar.isValidDay(29, 2, 2024).should.equal(true); + calendar.isValidDay(29, 2, 2100).should.equal(false); + calendar.isValidDay(29, 2, 2000).should.equal(true); + }); + }); + +});