From e4601576bbcad71462587f347ee393eb9b442be3 Mon Sep 17 00:00:00 2001 From: he-is-harry Date: Wed, 5 Mar 2025 10:51:34 -0500 Subject: [PATCH] Added support for HALF_VECTOR - Added functions in the Reader and Writer to read and write half vectors - Added helper functions in bignum.js to read and write half precision (16 bit) floating point numbers from the regular 64 bit numbers - Added unit and integration tests to test reading and writing of half vectors - Added unit tests for the conversion of half precision numbers to and from 64 bit numbers --- lib/protocol/ExecuteTask.js | 1 + lib/protocol/Reader.js | 6 + lib/protocol/Statement.js | 1 + lib/protocol/Writer.js | 13 +- lib/protocol/common/DataFormatVersion.js | 3 +- lib/protocol/common/NormalizedTypeCode.js | 2 + lib/protocol/common/ReadFunction.js | 2 + lib/protocol/common/TypeCode.js | 3 +- lib/util/bignum.js | 109 +++++++++ test/acceptance/db.DataType.js | 261 +++++++++++++++++++++- test/db/RemoteDB.js | 21 ++ test/fixtures/parametersData.js | 30 ++- test/lib.Reader.js | 22 +- test/lib.Writer.js | 23 +- test/util.bignum.js | 99 ++++++++ 15 files changed, 576 insertions(+), 20 deletions(-) diff --git a/lib/protocol/ExecuteTask.js b/lib/protocol/ExecuteTask.js index e2e8e1f..e7e6dfb 100644 --- a/lib/protocol/ExecuteTask.js +++ b/lib/protocol/ExecuteTask.js @@ -261,6 +261,7 @@ function createInvalidFunctionCodeError() { function isVector(type) { switch (type) { case TypeCode.REAL_VECTOR: + case TypeCode.HALF_VECTOR: return true; default: return false; diff --git a/lib/protocol/Reader.js b/lib/protocol/Reader.js index f8155d4..dc259ff 100644 --- a/lib/protocol/Reader.js +++ b/lib/protocol/Reader.js @@ -448,6 +448,12 @@ Reader.prototype.readRealVector = function readRealVector() { }); } +Reader.prototype.readHalfVector = function readHalfVector() { + return this.readVector(2, function (buffer, offset) { + return bignum.readDec16LE(buffer, offset); + }); +} + Reader.LobDescriptor = LobDescriptor; function LobDescriptor(type, options, charLength, byteLength, locatorId, chunk, defaultType) { diff --git a/lib/protocol/Statement.js b/lib/protocol/Statement.js index 8cdc5ef..a1b5d0e 100644 --- a/lib/protocol/Statement.js +++ b/lib/protocol/Statement.js @@ -212,6 +212,7 @@ function isOutputParameter(metadata) { function isVector(type) { switch (type) { case TypeCode.REAL_VECTOR: + case TypeCode.HALF_VECTOR: return true; default: return false; diff --git a/lib/protocol/Writer.js b/lib/protocol/Writer.js index 28cf501..9c66555 100644 --- a/lib/protocol/Writer.js +++ b/lib/protocol/Writer.js @@ -115,7 +115,7 @@ Writer.prototype.add = function add(type, value, fraction, length) { } else if (type === TypeCode.DECIMAL || type === TypeCode.FIXED8 || type === TypeCode.FIXED12 || type === TypeCode.FIXED16) { this[type](value, fraction); - } else if (type === TypeCode.REAL_VECTOR) { + } else if (type === TypeCode.REAL_VECTOR || type === TypeCode.HALF_VECTOR) { this[type](value, length); } else { this[type](value); @@ -457,6 +457,7 @@ Writer.prototype.pushNull = function pushNull(type) { break; case TypeCode.ST_GEOMETRY: case TypeCode.REAL_VECTOR: + case TypeCode.HALF_VECTOR: nullTypeCode = TypeCode.BINARY | 0x80; break; default: @@ -1002,6 +1003,16 @@ Writer.prototype[TypeCode.REAL_VECTOR] = function writeRealVector(value, length) this.writeVector(value, length, 4, writeRealElem, 'REAL_VECTOR'); } +Writer.prototype[TypeCode.HALF_VECTOR] = function writeHalfVector(value, length) { + function writeHalfElem (value, buffer, offset) { + if (!util.isNumber(value)) { + throw createInputError('HALF_VECTOR'); + } + bignum.writeDec16LE(buffer, value, offset); + } + this.writeVector(value, length, 2, writeHalfElem, 'HALF_VECTOR'); +} + function setChar(str, i, c) { if(i >= str.length) return str; return str.substring(0, i) + c + str.substring(i + 1); diff --git a/lib/protocol/common/DataFormatVersion.js b/lib/protocol/common/DataFormatVersion.js index b624f64..ac91ae3 100644 --- a/lib/protocol/common/DataFormatVersion.js +++ b/lib/protocol/common/DataFormatVersion.js @@ -24,6 +24,7 @@ module.exports = { LEVEL7: 7, LEVEL8: 8, LEVEL9: 9, + LEVEL10: 10, // Maximum data format version supported by this driver - MAX_VERSION: 9, + MAX_VERSION: 10, }; diff --git a/lib/protocol/common/NormalizedTypeCode.js b/lib/protocol/common/NormalizedTypeCode.js index c288578..cad0250 100644 --- a/lib/protocol/common/NormalizedTypeCode.js +++ b/lib/protocol/common/NormalizedTypeCode.js @@ -81,3 +81,5 @@ NormalizedTypeCode[TypeCode.ST_POINT] = TypeCode.ST_GEOMETRY; NormalizedTypeCode[TypeCode.BOOLEAN] = TypeCode.BOOLEAN; // RealVector NormalizedTypeCode[TypeCode.REAL_VECTOR] = TypeCode.REAL_VECTOR; +// HalfVector +NormalizedTypeCode[TypeCode.HALF_VECTOR] = TypeCode.HALF_VECTOR; diff --git a/lib/protocol/common/ReadFunction.js b/lib/protocol/common/ReadFunction.js index b347713..11c7826 100644 --- a/lib/protocol/common/ReadFunction.js +++ b/lib/protocol/common/ReadFunction.js @@ -42,6 +42,7 @@ var READ_FIXED8 = 'readFixed8'; var READ_FIXED12 = 'readFixed12'; var READ_FIXED16 = 'readFixed16'; var READ_REAL_VECTOR = 'readRealVector'; +var READ_HALF_VECTOR = 'readHalfVector'; ReadFunction[TypeCode.TINYINT] = READ_TINYINT; ReadFunction[TypeCode.SMALLINT] = READ_SMALLINT; @@ -83,3 +84,4 @@ ReadFunction[TypeCode.FIXED8] = READ_FIXED8; ReadFunction[TypeCode.FIXED12] = READ_FIXED12; ReadFunction[TypeCode.FIXED16] = READ_FIXED16; ReadFunction[TypeCode.REAL_VECTOR] = READ_REAL_VECTOR; +ReadFunction[TypeCode.HALF_VECTOR] = READ_HALF_VECTOR; diff --git a/lib/protocol/common/TypeCode.js b/lib/protocol/common/TypeCode.js index b664672..0e7abbb 100644 --- a/lib/protocol/common/TypeCode.js +++ b/lib/protocol/common/TypeCode.js @@ -89,5 +89,6 @@ module.exports = { FIXED16: 76, FIXED8: 81, FIXED12: 82, - REAL_VECTOR: 96 + REAL_VECTOR: 96, + HALF_VECTOR: 97 }; \ No newline at end of file diff --git a/lib/util/bignum.js b/lib/util/bignum.js index 7c37f59..9c331c9 100644 --- a/lib/util/bignum.js +++ b/lib/util/bignum.js @@ -71,6 +71,15 @@ var BIN_10_35_7 = 19; var UInt64Max = '18446744073709551615'; var UInt64MaxLen = 20; +/* overflow threshold for dec16 */ +// dec16 max value + 1/2 ulp +var Dec16Overflow = 65520; +/* underflow threshold for dec16 */ +// smallest non-zero dec16 value, half and smaller rounds to 0 +var Dec16Underflow = 5.9604645e-8 * 0.5; +var Dec64ExpBias = 1023; +var Dec16ExpBias = 15; + function _readInt64(buffer, offset, unsigned) { var x, y, s, y0, y1, x0, x1, x2; @@ -831,6 +840,104 @@ function readFIXED(buffer, bytes, offset, frac) { } } +function writeDec16(buffer, value, offset) { + offset = offset || 0; + buffer.fill(0x00, offset, offset + 2); + + var buffer64 = Buffer.alloc(8); + buffer64.writeDoubleLE(value); + + // Write sign bit + if (value < 0) { + buffer[offset + 1] |= 0x80; + } + + if (isNaN(value)) { + // Write max exponent + 1 + buffer[offset + 1] |= 0x7c; + // Indicate NaN + buffer[offset + 1] |= 0x02; + // Attempt to preserve higher order 10 significand bits + buffer[offset + 1] |= (buffer64[6] & 0x0c) >> 2; + buffer[offset] |= (buffer64[6] & 0x03) << 6 | (buffer[5] & 0xfc) >> 2; + return ; + } + + var abs = Math.abs(value); + if (abs >= Dec16Overflow) { + buffer[offset + 1] |= 0x7c; // Positive or negative infinity + return ; + } + + if (abs <= Dec16Underflow) { + // Round to 0 + return ; + } + + // exp should be limited to -25 to 15 given the overflow and underflow bounds + var exp = ((buffer64[7] & 0x7f) << 4 | (buffer64[6] & 0xf0) >> 4) - Dec64ExpBias; + + // For subnormals, for computations in rounding, retain the + // difference expdelta = E_min - exp and the hidden msb in the + // 64 bit representation. + var expdelta = 0; + var msb = 0; + if (exp < -14) { + // Subnormal + expdelta = -14 - exp; + exp = -15; + // Set the hidden msb in the bit after the mantissa + msb = 0x0010_0000; + } + + var top_bits = buffer64[4] + (buffer64[5] << 8) + ((buffer64[6] & 0x0f) << 16) | msb; + var bottom_bits = buffer64[0] + (buffer64[1] << 8) + (buffer64[2] << 16) + (buffer64[3] << 24 >>> 0); + + // Extract 10 significant bits starting at 42 + expdelta, top half starts at 32 + var signif_bits = top_bits >> (10 + expdelta); + + // Round to nearest even, determining to round up is a function + // of the least significant bit (lsb), next bit (round position), + // and sticky bit (if there are non-zero bits to the right of the + // round). Increments occur in 3 cases: + // LSB Round Sticky + // 0 1 1 + // 1 1 0 + // 1 1 1 + var lsb = top_bits & (1 << 10 + expdelta); + var round = top_bits & (1 << 9 + expdelta); + var sticky = (bottom_bits != 0) | (top_bits & ((1 << 9 + expdelta) - 1)); + + if (round != 0 && ((lsb | sticky) != 0)) { + signif_bits++; + } + + // Add exponent to significand in case of rounding increasing the + // exponent + signif_bits += (exp + 15) << 10; + + buffer[offset + 1] |= (signif_bits >> 8) & 0x7f; + buffer[offset] |= signif_bits & 0xff; +} + +function readDec16(buffer, offset) { + var s = buffer[offset + 1] & 0x80; + var e = (buffer[offset + 1] & 0x7c) >> 2; + var m = buffer[offset] + ((buffer[offset + 1] & 0x03) << 8); + + if (e === 0) { // subnormal + e = 1 - Dec16ExpBias; + } else if (e === 0x1f) { + return m ? NaN : ((s ? -1 : 1) * Infinity); + } else { + m |= 1 << 10; // add hidden msb + e = e - Dec16ExpBias; + } + + // Mantissa size is 10 + return (s ? -1 : 1) * m * Math.pow(2, e - 10); +} + exports.readInt64LE = readInt64; exports.readUInt64LE = readUInt64; exports.writeInt64LE = writeInt64; @@ -842,3 +949,5 @@ exports.writeDec128 = writeDec128; exports.readDecFloat = readDecFloat; exports.readDecFixed = readDecFixed; exports.readFIXED = readFIXED; +exports.writeDec16LE = writeDec16; +exports.readDec16LE = readDec16; diff --git a/test/acceptance/db.DataType.js b/test/acceptance/db.DataType.js index 26f71b2..b81e6d1 100644 --- a/test/acceptance/db.DataType.js +++ b/test/acceptance/db.DataType.js @@ -16,7 +16,7 @@ var async = require('async'); // Set the data format version necessary for the data types -const ORGINAL_DATA_FORMAT = 9; +const ORGINAL_DATA_FORMAT = 10; var db = require('../db')({dataFormatSupport: ORGINAL_DATA_FORMAT}); var RemoteDB = require('../db/RemoteDB'); var lorem = require('../fixtures/lorem'); @@ -181,6 +181,46 @@ describe('db', function () { async.waterfall([prepare, insert, drop], callback); } + /* + Tests the swapping of data types with the TYPE_SWAP_PROC procedure + - values: Parameters to swap + - expected: Expected parameters output + - dataTypeCodes: Expected data type code(s) of the parameter data types + - done: Done callback to end the test + */ + function testDataTypeSwapProc(values, expected, dataTypeCodes, done) { + var statement; + function prepare(cb) { + client.prepare('CALL TYPE_SWAP_PROC (?, ?)', function (err, ps) { + if (err) done(err); + statement = ps; + cb(err); + }); + } + + function validate(cb) { + var metadata = statement.parameterMetadata; + metadata.should.have.length(2); + metadata[0].should.have.property('dataType').which.is.oneOf(dataTypeCodes); + metadata[1].should.have.property('dataType').which.is.oneOf(dataTypeCodes); + + statement.exec(values, function (err, params) { + if (err) done(err); + params.should.eql(expected); + cb(err); + }); + } + + function dropStatement(cb) { + statement.drop(function (err) { + // ignore error + cb(); + }); + } + + async.waterfall([prepare, validate, dropStatement], done); + } + // Functions used to setup and drop tables only when the db is a RemoteDB function setUpTableRemoteDB(tableName, columns, dataFormatRequired) { // Returns a closure which has access to the parameters above and the 'this' @@ -1117,7 +1157,7 @@ describe('db', function () { }); }); - describeRemoteDB('REAL_VECTOR (only tested on HANA cloud)', function () { + describeRemoteDB('REAL_VECTOR, HALF_VECTOR (only tested on HANA cloud)', function () { var skipTests = false; before(function (done) { var version = db.getHANAFullVersion(); @@ -1132,29 +1172,29 @@ describe('db', function () { function setUpTable(tableName, columns, dataFormatRequired) { // Returns a closure which has access to the parameters above and the 'this' // argument will be the describe context when used in a mocha before hook - return function setUpTableClosure(done) { + return function setUpTableClosure(done, cb) { if (skipTests || db.getDataFormatVersion2() < dataFormatRequired) { this.skip(); done(); } else { setCurTableCols(columns); - db.createTable.bind(db)(tableName, ['ID INT GENERATED BY DEFAULT AS IDENTITY'].concat(columns), null, done); + db.createTable.bind(db)(tableName, ['ID INT GENERATED BY DEFAULT AS IDENTITY'].concat(columns), null, cb ? cb : done); } } } function dropTable(tableName, dataFormatRequired) { - return function dropTableClosure(done) { + return function dropTableClosure(done, cb) { if (skipTests || db.getDataFormatVersion2() < dataFormatRequired) { done(); } else { - db.dropTable.bind(db)(tableName, done); + db.dropTable.bind(db)(tableName, cb ? cb : done); } } } function changeSettingReconnect(settings, dataFormatRequired) { - return function changeSettingReconnectClosure(done) { + return function changeSettingReconnectClosure(done, cb) { if (skipTests || db.getDataFormatVersion2() < dataFormatRequired) { done(); } else { @@ -1162,7 +1202,7 @@ describe('db', function () { db.client.set(key, settings[key]); } db.end(function (err) { - db.init(done); + db.init(cb ? cb : done); }); } } @@ -1170,8 +1210,18 @@ describe('db', function () { describe('REAL_VECTOR', function () { describe('dynamic length', function () { - before(setUpTable('REAL_VECTOR_TABLE', ['A REAL_VECTOR'], 9)); - after(dropTable('REAL_VECTOR_TABLE', 9)); + before(function (done) { + setUpTable('REAL_VECTOR_TABLE', ['A REAL_VECTOR'], 9).apply(this, [done, function (err) { + if (err) done(err); + db.createSwapTypeProc('REAL_VECTOR', done); + }]); + }); + after(function (done) { + dropTable('REAL_VECTOR_TABLE', 9).apply(this, [done, function (err) { + if (err) done(err); + db.dropSwapTypeProc(done); + }]); + }); it('should return valid real vectors via callback', function (done) { var insertValues = [ @@ -1212,11 +1262,28 @@ describe('db', function () { ]; async.each(invalidTestData, testDataTypeError.bind(null, 'REAL_VECTOR_TABLE'), done); }); + + it('should call a procedure with real vector params', function (done) { + var insertValues = [[1, 2, 3], [4, 5, 6]]; + var expected = {A: Buffer.from("03000000000080400000A0400000C040", "hex"), + B: Buffer.from("030000000000803F0000004000004040", "hex")}; + testDataTypeSwapProc(insertValues, expected, [96], done); + }); }); describe('vectorOutputType Array', function () { - before(changeSettingReconnect({ vectorOutputType: 'Array', packetSizeLimit: Math.pow(2, 19) }, 9)); - after(changeSettingReconnect({ vectorOutputType: 'Buffer', packetSizeLimit: DEFAULT_PACKET_SIZE }, 9)); + before(function (done) { + changeSettingReconnect({ vectorOutputType: 'Array', packetSizeLimit: Math.pow(2, 19) }, 9)(done, function (err) { + if (err) done(err); + db.createSwapTypeProc('REAL_VECTOR', done); + }); + }); + after(function (done) { + changeSettingReconnect({ vectorOutputType: 'Buffer', packetSizeLimit: DEFAULT_PACKET_SIZE }, 9)(done, function (err) { + if (err) done(err); + db.dropSwapTypeProc(done); + }); + }); beforeEach(setUpTable('REAL_VECTOR_TABLE', ['A REAL_VECTOR'], 9)); afterEach(dropTable('REAL_VECTOR_TABLE', 9)); @@ -1241,6 +1308,12 @@ describe('db', function () { var expected = [{A: buildVector}]; testDataTypeValid('REAL_VECTOR_TABLE', insertValues, [96], expected, done); }); + + it('should call a procedure with real vector params', function (done) { + var insertValues = [[1, 2, 3], [4, 5, 6]]; + var expected = {A: [4, 5, 6], B: [1, 2, 3]}; + testDataTypeSwapProc(insertValues, expected, [96], done); + }); }); describe('fixed length', function () { @@ -1286,5 +1359,169 @@ describe('db', function () { }); }); }); + + describe('HALF_VECTOR', function () { + describe('dynamic length', function () { + before(function (done) { + setUpTable('HALF_VECTOR_TABLE', ['A HALF_VECTOR'], 10).apply(this, [done, function (err) { + if (err) done(err); + db.createSwapTypeProc('HALF_VECTOR', done); + }]); + }); + after(function (done) { + dropTable('HALF_VECTOR_TABLE', 10).apply(this, [done, function (err) { + if (err) done(err); + db.dropSwapTypeProc(done); + }]); + }); + + it('should return valid half vectors via callback', function (done) { + var insertValues = [ + [Buffer.from("050000008F773F4747E83B363EC2", "hex")], + [[0]], + [[-100, 200, -3000.458]], + [[5.960464477539063e-8, -5.960464477539063e-8, 0.00006097555160522461, -0.00006103515625, 1, -1, 65504, -65504]], + [[0.01177, -5.6832284806445, 64438.18, -59.801068263, -425.9631]], + [null], + ]; + var expected = [{A: Buffer.from("050000008F773F4747E83B363EC2", "hex")}, {A: Buffer.from("010000000000", "hex")}, + {A: Buffer.from("0300000040D6405ADCE9", "hex")}, {A: Buffer.from("0800000001000180FF030084003C00BCFF7BFFFB", "hex")}, + {A: Buffer.from("050000000722AFC5DE7B7AD3A8DE", "hex")}, {A: null}]; + testDataTypeValid('HALF_VECTOR_TABLE', insertValues, [97], expected, done); + }); + + it('should raise input type error', function (done) { + var invalidTestData = [ + { + value: 5, + errMessage: 'Cannot set parameter at row: 1. Wrong input for HALF_VECTOR type' + }, + { + value: ['3.12'], + errMessage: 'Cannot set parameter at row: 1. Wrong input for HALF_VECTOR type' + }, + { + value: Buffer.from("010000003F"), + errMessage: 'Cannot set parameter at row: 1. Invalid length or indicator value for HALF_VECTOR type' + }, + { + value: Buffer.from("00000000"), + errMessage: 'Cannot set parameter at row: 1. Invalid length or indicator value for HALF_VECTOR type' + }, + { + value: Buffer.from("02000000003C00400042"), + errMessage: 'Cannot set parameter at row: 1. Invalid length or indicator value for HALF_VECTOR type' + }, + {value: [], errMessage: 'Cannot set parameter at row: 1. Invalid length or indicator value for HALF_VECTOR type'}, + { + value: [Infinity], + errMessage: 'Invalid vector format: exception 337056: Found a non-finite vector element at offset 4\n: type_code=12, index=1' + }, + ]; + async.each(invalidTestData, testDataTypeError.bind(null, 'HALF_VECTOR_TABLE'), done); + }); + + it('should call a procedure with half vector params', function (done) { + var insertValues = [[1, 2, 3], [4, 5, 6]]; + var expected = {A: Buffer.from("03000000004400450046", "hex"), + B: Buffer.from("03000000003C00400042", "hex")}; + testDataTypeSwapProc(insertValues, expected, [97], done); + }); + }); + + describe('vectorOutputType Array', function () { + before(function (done) { + changeSettingReconnect({ vectorOutputType: 'Array', packetSizeLimit: Math.pow(2, 19) }, 10)(done, function (err) { + if (err) done(err); + db.createSwapTypeProc('HALF_VECTOR', done); + }); + }); + after(function (done) { + changeSettingReconnect({ vectorOutputType: 'Buffer', packetSizeLimit: DEFAULT_PACKET_SIZE }, 10)(done, function (err) { + if (err) done(err); + db.dropSwapTypeProc(done); + }); + }); + beforeEach(setUpTable('HALF_VECTOR_TABLE', ['A HALF_VECTOR'], 10)); + afterEach(dropTable('HALF_VECTOR_TABLE', 10)); + + it('should return valid real vectors as arrays', function (done) { + var insertValues = [ + [null], + [[0]], + [Buffer.from("01000000B757", "hex")], + [[52871.139642, 4614.4, -340.8256, -28.77, 5.81370, -0.0233050, -0.002193225, 0.0000405891525]], + [Buffer.from("05000000003C0040004200440045", "hex")], + // Rounding test, between 32768 and 65520, precision is limited to 32, so round to nearest + // even multiple of 32 + [[64400, 64400.001, 64399.999999, 64438.18, 64432, 64431.999999]], + ]; + var expected = [{A: null}, {A: [0]}, {A: [123.4375]}, {A: [52864, 4616, -340.75, + -28.765625, 5.8125, -0.0233001708984375, -0.002193450927734375, 4.0590763092041016e-05]}, + {A: [1, 2, 3, 4, 5]}, {A: [64384, 64416, 64384, 64448, 64448, 64416]}]; + testDataTypeValid('HALF_VECTOR_TABLE', insertValues, [97], expected, done); + }); + + it('should insert and return maximum size real vectors', function (done) { + this.timeout(5000); + // Mod to maintain precision of at least 1 + var buildVector = Array.from(Array(65000).keys()).map(function (x) { + return x % 2048; + }); + var insertValues = [buildVector]; + var expected = [{A: buildVector}]; + testDataTypeValid('HALF_VECTOR_TABLE', insertValues, [97], expected, done); + }); + + it('should call a procedure with half vector params', function (done) { + var insertValues = [[1, 2, 3], [4, 5, 6]]; + var expected = {A: [4, 5, 6], B: [1, 2, 3]}; + testDataTypeSwapProc(insertValues, expected, [97], done); + }); + }); + + describe('fixed length', function () { + before(setUpTable('HALF_VECTOR_TABLE', ['A HALF_VECTOR(3)'], 10)); + after(dropTable('HALF_VECTOR_TABLE', 10)); + + it('should return valid half vectors via callback', function (done) { + var insertValues = [ + [[0, 0, 0]], + [Buffer.from("030000004056405AB05C", "hex")], + [[-39144.50268, -135.3234127, 4.4220961]], + [[-0.30908203125, 0.0061, 0.0000142]], + [[5.960464477539063e-8, 0.00006103515625, 65504]], + [[-5.960464477539063e-8, -0.00006103515625, -65504]], + [null], + ]; + var expected = [{A: Buffer.from("03000000000000000000", "hex")}, {A: Buffer.from("030000004056405AB05C", "hex")}, + {A: Buffer.from("03000000C7F83BD86C44", "hex")}, {A: Buffer.from("03000000F2B43F1EEE00", "hex")}, + {A: Buffer.from("0300000001000004FF7B", "hex")}, {A: Buffer.from("0300000001800084FFFB", "hex")}, {A: null}]; + testDataTypeValid('HALF_VECTOR_TABLE', insertValues, [97], expected, done); + }); + + it('should raise input type error', function (done) { + var invalidTestData = [ + { + value: Buffer.from("03000000B057205F2A62004A645D", "hex"), + errMessage: 'Cannot set parameter at row: 1. Invalid length or indicator value for HALF_VECTOR type' + }, + { + value: [3, 4, 5, 6, 7], + errMessage: 'Cannot set parameter at row: 1. The source dimension is different from the target dimension for HALF_VECTOR type' + }, + { + value: [1, 2], + errMessage: 'Cannot set parameter at row: 1. The source dimension is different from the target dimension for HALF_VECTOR type' + }, + { + value: Buffer.from("010000009E74", "hex"), + errMessage: 'Cannot set parameter at row: 1. The source dimension is different from the target dimension for HALF_VECTOR type' + }, + ]; + async.each(invalidTestData, testDataTypeError.bind(null, 'HALF_VECTOR_TABLE'), done); + }); + }); + }); }); }); \ No newline at end of file diff --git a/test/db/RemoteDB.js b/test/db/RemoteDB.js index 87d5883..bdf2fa7 100644 --- a/test/db/RemoteDB.js +++ b/test/db/RemoteDB.js @@ -211,3 +211,24 @@ RemoteDB.prototype.createHashBlobProc = function createHashBlobProc(cb) { RemoteDB.prototype.dropHashBlobProc = function dropHashBlobProc(cb) { this.client.exec('drop procedure HASH_BLOB cascade', cb); }; + +RemoteDB.prototype.createSwapTypeProc = function createSwapTypeProc(type, cb) { + var sql = [ + 'create procedure TYPE_SWAP_PROC (', + ` inout A ${type}, inout B ${type}) AS`, + 'begin', + ` declare TMP ${type} := A;`, + ' A = B;', + ' B = TMP;', + 'end;' + ].join('\n'); + var self = this; + self.dropSwapTypeProc(function() { + // ignore any error as the procedure may not exist yet + self.client.exec(sql, cb); + }); +} + +RemoteDB.prototype.dropSwapTypeProc = function dropSwapTypeProc(cb) { + this.client.exec('drop procedure TYPE_SWAP_PROC cascade', cb); +}; diff --git a/test/fixtures/parametersData.js b/test/fixtures/parametersData.js index bbe1322..4248e42 100644 --- a/test/fixtures/parametersData.js +++ b/test/fixtures/parametersData.js @@ -870,7 +870,13 @@ exports.VECTOR = { '0C080100000000000000' + '0C0801000000FFFF7F7F' + '0C1003000000955AE741B2778447330B47C6' + - '0C2007000000A4D2F0CA603FB44262CD0139EE58154500EC49C6005B73497F96184B', 'hex') + '0C2007000000A4D2F0CA603FB44262CD0139EE58154500EC49C6005B73497F96184B' + + '8C' + + '0C0802000000004099C5' + + '0C06010000000000' + + '0C0C0400000001800100FFFBFF7B' + + '0C0A03000000203F18AD59DC' + + '0C140800000031FBA2550E080A80AB684FF22F73C078', 'hex') }, types: [ TypeCode.REAL_VECTOR, @@ -878,7 +884,13 @@ exports.VECTOR = { TypeCode.REAL_VECTOR, TypeCode.REAL_VECTOR, TypeCode.REAL_VECTOR, - TypeCode.REAL_VECTOR + TypeCode.REAL_VECTOR, + TypeCode.HALF_VECTOR, + TypeCode.HALF_VECTOR, + TypeCode.HALF_VECTOR, + TypeCode.HALF_VECTOR, + TypeCode.HALF_VECTOR, + TypeCode.HALF_VECTOR, ], lengths: [ 0, @@ -887,6 +899,12 @@ exports.VECTOR = { 0, 3, 0, + 0, + 2, + 1, + 0, + 3, + 0, ], values: [ null, @@ -894,6 +912,12 @@ exports.VECTOR = { [0], [3.40282347e+38], [28.91923, 67823.39, -12738.80], - [-7891282, 90.123781, 0.0001237891, 2389.558, -12923.00, 996784, 9999999] + [-7891282, 90.123781, 0.0001237891, 2389.558, -12923.00, 996784, 9999999], + null, + Buffer.from("02000000004099C5", 'hex'), + [0], + [-0.000000059604645, 0.000000059604645, -65504, 65504], + [1.7812479, -0.0796, -278.34], + [-58912, 90.123781, 0.0001237891, -0.0000005892, 2389.558, -12923.00, 14708.001, 38928], ] } diff --git a/test/lib.Reader.js b/test/lib.Reader.js index 053aa46..d518d20 100644 --- a/test/lib.Reader.js +++ b/test/lib.Reader.js @@ -440,7 +440,7 @@ describe('Lib', function () { reader.hasMore().should.equal(false); }); - it('should read a real vector', function () { + it('should read a REAL_VECTOR', function () { var buffer = new Buffer([ 0xFF, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -459,6 +459,26 @@ describe('Lib', function () { } reader.hasMore().should.equal(false); }); + + it('should read a HALF_VECTOR', function () { + var buffer = new Buffer([ + 0xFF, + 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x03, 0x00, 0x00, 0x00, 0xD0, 0xE3, 0xD0, 0x67, 0xDC, 0xE9, + 0x0C, 0x04, 0x00, 0x00, 0x00, 0x84, 0xF5, 0x33, 0x60, 0x4D, 0x97, 0xC9, 0x3C, + 0x12, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0x83, 0x00, 0x04, 0x00, 0x3C, 0x00, 0xBC, 0xFF, 0x7B, 0xFF, 0xFB, + ]); + var reader = new lib.Reader(buffer, null, { vectorOutputType: 'Array' }); + (reader.readHalfVector() === null).should.be.ok; + var expected = [[0], [-1000, 2000, -3000], [-22592, 537.5, -0.0017824172973632812, 1.1962890625], + [5.960464477539063e-8, -0.00006097555160522461, 0.00006103515625, 1, -1, 65504, -65504]]; + for (var i = 0; i < expected.length; i++) { + var result = reader.readHalfVector(); + result.should.have.length(expected[i].length); + result.should.eql(expected[i]); + } + reader.hasMore().should.equal(false); + }); }); it('should read a BLob', function () { diff --git a/test/lib.Writer.js b/test/lib.Writer.js index a65e944..3804688 100644 --- a/test/lib.Writer.js +++ b/test/lib.Writer.js @@ -766,8 +766,10 @@ describe('Lib', function () { Writer.prototype.setValues.bind(writer, [Buffer.from('0300000000803F0000004000004040', 'hex')]).should.throw(); // Fvecs length does not match Writer.prototype.setValues.bind(writer, [Buffer.from('050000000000803F0000004000004040', 'hex')]).should.throw(); - // Vector is empty + // Vector is too small Writer.prototype.setValues.bind(writer, [Buffer.from('0000', 'hex')]).should.throw(); + // Vector is empty + Writer.prototype.setValues.bind(writer, [Buffer.from('00000000', 'hex')]).should.throw(); Writer.prototype.setValues.bind(writer, [[]]).should.throw(); var fixedWriter = new Writer({ types: [TypeCode.REAL_VECTOR], lengths: [3] }); @@ -777,6 +779,25 @@ describe('Lib', function () { Writer.prototype.setValues.bind(fixedWriter, [Buffer.from("050000000000803F0000004000004040000080400000A040", "hex")]).should.throw(); Writer.prototype.setValues.bind(fixedWriter, [[1, 2, 3, 4, 5]]).should.throw(); }); + + it('should raise wrong input type error for HALF_VECTOR', function () { + var writer = new Writer({ types: [TypeCode.HALF_VECTOR], lengths: [0] }); + Writer.prototype.setValues.bind(writer, [false]).should.throw(); + // Buffer length - 4 is not divisible by 2 + Writer.prototype.setValues.bind(writer, [Buffer.from('0300000003C00400042', 'hex')]).should.throw(); + // Fvecs length does not match + Writer.prototype.setValues.bind(writer, [Buffer.from('05000000B4CC696FAF63DD87', 'hex')]).should.throw(); + // Vector is empty + Writer.prototype.setValues.bind(writer, [Buffer.from('00000000', 'hex')]).should.throw(); + Writer.prototype.setValues.bind(writer, [[]]).should.throw(); + + var fixedWriter = new Writer({ types: [TypeCode.REAL_VECTOR], lengths: [3] }); + // Buffer length - 4 is not divisible by 2 + Writer.prototype.setValues.bind(fixedWriter, [Buffer.from('0300000003C00400042', 'hex')]).should.throw(); + // Length does not match expected + Writer.prototype.setValues.bind(fixedWriter, [Buffer.from("05000000B4CC696FAF63DD87404E", "hex")]).should.throw(); + Writer.prototype.setValues.bind(fixedWriter, [[1, 2, 3, 4, 5]]).should.throw(); + }); }); }); diff --git a/test/util.bignum.js b/test/util.bignum.js index 3864881..76e6a2a 100644 --- a/test/util.bignum.js +++ b/test/util.bignum.js @@ -77,6 +77,16 @@ function readUInt128(hex) { return bignum.readUInt128LE(new Buffer(hex, 'hex'), 0); } +function readDec16(hex) { + return bignum.readDec16LE(new Buffer(hex, "hex"), 0); +} + +function writeDec16(value) { + var buffer = new Buffer(2); + bignum.writeDec16LE(buffer, value, 0); + return buffer.toString('hex'); +} + describe('Util', function () { describe('#Int64', function () { @@ -468,4 +478,93 @@ describe('Util', function () { }); }); + describe('#Decimal16', function () { + it('should read special values', function () { + readDec16("0000").should.equal(0); + readDec16("0080").should.equal(0); + readDec16("007C").should.equal(Infinity); + readDec16("00FC").should.equal(-Infinity); + readDec16("017C").should.be.NaN(); + readDec16("007E").should.be.NaN(); + readDec16("00FE").should.be.NaN(); + readDec16("FFFF").should.be.NaN(); + }); + + it('should read positive numbers', function () { + readDec16("003C").should.equal(1); + readDec16("487A").should.equal(51456); + readDec16("4E5A").should.equal(201.75); + readDec16("F545").should.equal(5.95703125); + readDec16("6025").should.equal(0.02099609375); + readDec16("6A18").should.equal(0.002155303955078125); + readDec16("FF03").should.equal(0.00006097555160522461); + readDec16("0100").should.equal(5.960464477539063e-8); + readDec16("FF7B").should.equal(65504); + }); + + it('should read negative numbers', function () { + readDec16("00BC").should.equal(-1); + readDec16("7DF9").should.equal(-44960); + readDec16("26DD").should.equal(-329.5); + readDec16("D3D1").should.equal(-46.59375); + readDec16("14C1").should.equal(-2.5390625); + readDec16("16B2").should.equal(-0.190185546875); + readDec16("C79D").should.equal(-0.005641937255859375); + readDec16("E28F").should.equal(-0.0004811286926269531); + readDec16("FF83").should.equal(-0.00006097555160522461); + readDec16("0180").should.equal(-5.960464477539063e-8); + readDec16("FFFB").should.equal(-65504); + }); + + it('should write special values', function () { + writeDec16(0).should.equal("0000"); + writeDec16(Infinity).should.equal("007c"); + writeDec16(-Infinity).should.equal("00fc"); + // Exceed max value + writeDec16(65525).should.equal("007c"); + writeDec16(-65525).should.equal("00fc"); + // Smaller than smallest subnormal + writeDec16(2.98023225e-08).should.equal("0000"); + writeDec16(-2.98023225e-08).should.equal("0080"); + writeDec16(NaN).should.equal("007e"); + }); + + it('should write positive numbers', function () { + writeDec16(1).should.equal("003c"); + writeDec16(29370.67608).should.equal("2c77"); + writeDec16(2349.75188).should.equal("9768"); + writeDec16(375.383).should.equal("de5d"); + writeDec16(53.098625).should.equal("a352"); + writeDec16(5.25966403).should.equal("4245"); + writeDec16(0.36067469).should.equal("c535"); + writeDec16(0.01).should.equal("1f21"); + writeDec16(0.005481234).should.equal("9d1d"); + writeDec16(64400).should.equal("dc7b"); + writeDec16(64432).should.equal("de7b"); + writeDec16(0.000060975552).should.equal("ff03"); + writeDec16(5.960464477539063e-8).should.equal("0100"); + writeDec16(65504).should.equal("ff7b"); + writeDec16(65519).should.equal("ff7b"); + }); + + it('should write negative numbers', function () { + writeDec16(-1).should.equal("00bc"); + writeDec16(-63056.71).should.equal("b3fb"); + writeDec16(-4778.2248).should.equal("abec"); + writeDec16(-131.0761).should.equal("19d8"); + writeDec16(-2.0531413).should.equal("1bc0"); + writeDec16(-0.842).should.equal("bcba"); + writeDec16(-0.07665778).should.equal("e8ac"); + writeDec16(-0.04734).should.equal("0faa"); + writeDec16(-0.000349921).should.equal("bc8d"); + writeDec16(-0.0000579627878).should.equal("cc83"); + writeDec16(-64400).should.equal("dcfb"); + writeDec16(-64432).should.equal("defb"); + writeDec16(-0.000060975552).should.equal("ff83"); + writeDec16(-5.960464477539063e-8).should.equal("0180"); + writeDec16(-65504).should.equal("fffb"); + writeDec16(-65519).should.equal("fffb"); + }); + }); + }); \ No newline at end of file