Skip to content

Commit dd2a7b6

Browse files
committed
Added support for FIXED types
- Added functions to read and write FIXED8, FIXED12, and FIXED16 types - Modified Statement.js and Writer.js to pass the fraction metadata to the Writer for use in writing FIXED types - Added unit tests for the new FIXED type functions in bignum.js, Reader.js and Writer.js - Added integration tests for the new FIXED types
1 parent d6a27e3 commit dd2a7b6

18 files changed

+693
-67
lines changed

lib/protocol/ExecuteTask.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ function ExecuteTask(connection, options, callback) {
3030
this.scrollableCursor = options.scrollableCursor;
3131
this.statementId = options.statementId;
3232
this.functionCode = options.functionCode;
33-
this.writer = new Writer(options.parameters.types, connection.useCesu8, connection.spatialTypes);
33+
this.writer = new Writer(
34+
{ types: options.parameters.types, fractions: options.parameters.fractions },
35+
{ useCesu8: connection.useCesu8, spatialTypes: connection.spatialTypes }
36+
);
3437
var values = options.parameters.values;
3538
if (values.length && Array.isArray(values[0])) {
3639
this.parameterValues = values.slice();

lib/protocol/Parser.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ function createHonestParseFunction(metadata, options) {
109109

110110
function addReadFunction(column) {
111111
var args = [];
112-
if (column.dataType === TypeCode.DECIMAL) {
112+
if (column.dataType === TypeCode.DECIMAL || column.dataType === TypeCode.FIXED8
113+
|| column.dataType === TypeCode.FIXED12 || column.dataType === TypeCode.FIXED16) {
113114
args.push(column.fraction);
114115
}
115116
column.f = {
@@ -154,7 +155,8 @@ function createFunctionBody(metadata, options) {
154155

155156
function getReadFunction(column) {
156157
var fn = ReadFunction[column.dataType];
157-
if (column.dataType === TypeCode.DECIMAL) {
158+
if (column.dataType === TypeCode.DECIMAL || column.dataType === TypeCode.FIXED8
159+
|| column.dataType === TypeCode.FIXED12 || column.dataType === TypeCode.FIXED16) {
158160
fn += '(' + column.fraction + ')';
159161
} else {
160162
fn += '()';

lib/protocol/Reader.js

+27
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,33 @@ Reader.prototype.readDecimal = function readDecimal(fraction) {
365365
return value;
366366
};
367367

368+
Reader.prototype.readFixed8 = function readFixed8(fraction) {
369+
if (this.buffer[this.offset++] === 0x00) {
370+
return null;
371+
}
372+
var value = bignum.readFIXED(this.buffer, 8, this.offset, fraction);
373+
this.offset += 8;
374+
return value;
375+
}
376+
377+
Reader.prototype.readFixed12 = function readFixed12(fraction) {
378+
if (this.buffer[this.offset++] === 0x00) {
379+
return null;
380+
}
381+
var value = bignum.readFIXED(this.buffer, 12, this.offset, fraction);
382+
this.offset += 12;
383+
return value;
384+
}
385+
386+
Reader.prototype.readFixed16 = function readFixed16(fraction) {
387+
if (this.buffer[this.offset++] === 0x00) {
388+
return null;
389+
}
390+
var value = bignum.readFIXED(this.buffer, 16, this.offset, fraction);
391+
this.offset += 16;
392+
return value;
393+
}
394+
368395
Reader.prototype.readAlphanum = function readAlphanum() {
369396
return this.readBytes(false, true);
370397
}

lib/protocol/Statement.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,14 @@ Statement.prototype._normalizeInputParameters = function _normalizeInputParamete
112112
return values[metadata.name];
113113
}
114114

115+
function getTypeFraction(metadata) {
116+
return metadata.fraction;
117+
}
118+
115119
var parameters = {
116120
types: inputParameterMetadata.map(getDataType),
117-
values: undefined
121+
values: undefined,
122+
fractions: inputParameterMetadata.map(getTypeFraction),
118123
};
119124

120125
parameters.values = Array.isArray(values) ? values : inputParameterMetadata.filter(isDefined).map(getObjectValue);

lib/protocol/Writer.js

+132-20
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var LobOptions = common.LobOptions;
2121
var NormalizedTypeCode = common.NormalizedTypeCode;
2222
var bignum = util.bignum;
2323
var calendar = util.calendar;
24+
var zeropad = require('../util/zeropad');
2425
var isValidDay = calendar.isValidDay;
2526
var isValidTime = calendar.isValidTime;
2627
var isZeroDay = calendar.isZeroDay;
@@ -39,12 +40,14 @@ var REGEX = {
3940
};
4041

4142
const maxDecimalMantissaLen = 34;
43+
const maxFixedMantissaLen = 38;
4244

43-
function Writer(types, useCesu8, spatialTypes) {
44-
this._types = types.map(normalizeType);
45+
function Writer(params, options) {
46+
this._types = params.types.map(normalizeType);
47+
this._fractions = params.fractions;
4548
this.reset();
46-
this._useCesu8 = (useCesu8 === true);
47-
this._spatialTypes = (spatialTypes === 1 ? 1 : 0);
49+
this._useCesu8 = (options && options.useCesu8 === true);
50+
this._spatialTypes = ((options && options.spatialTypes === 1) ? 1 : 0);
4851
}
4952

5053
function normalizeType(type) {
@@ -66,13 +69,13 @@ Writer.prototype.reset = function reset() {
6669
Writer.prototype.setValues = function setValues(values) {
6770
this.reset();
6871
for (var i = 0; i < values.length; i++) {
69-
this.add(this._types[i], values[i]);
72+
this.add(this._types[i], values[i], this._fractions ? this._fractions[i] : undefined);
7073
}
7174
this._params = true;
7275
};
7376

74-
exports.create = function createWriter(params, useCesu8, spatialTypes) {
75-
var writer = new Writer(params.types, useCesu8, spatialTypes);
77+
exports.create = function createWriter(params, options) {
78+
var writer = new Writer(params, options);
7679
writer.setValues(params.values);
7780
return writer;
7881
};
@@ -95,9 +98,11 @@ Object.defineProperties(Writer.prototype, {
9598
}
9699
});
97100

98-
Writer.prototype.add = function add(type, value) {
101+
Writer.prototype.add = function add(type, value, fraction) {
99102
if (typeof value === 'undefined' || value === null) {
100103
this.pushNull(type);
104+
} else if (type === TypeCode.FIXED8 || type === TypeCode.FIXED12 || type === TypeCode.FIXED16) {
105+
this[type](value, fraction);
101106
} else {
102107
this[type](value);
103108
}
@@ -491,9 +496,9 @@ Writer.prototype[TypeCode.DOUBLE] = function writeDouble(value) {
491496
Writer.prototype[TypeCode.DECIMAL] = function writeDecimal(value) {
492497
var decimal;
493498
if (util.isString(value)) {
494-
decimal = stringToDecimal(value);
499+
decimal = stringToDecimal(value, maxDecimalMantissaLen);
495500
} else if (util.isNumber(value)) {
496-
decimal = stringToDecimal(value.toExponential());
501+
decimal = stringToDecimal(value.toExponential(), maxDecimalMantissaLen);
497502
} else {
498503
throw createInputError('DECIMAL');
499504
}
@@ -503,6 +508,100 @@ Writer.prototype[TypeCode.DECIMAL] = function writeDecimal(value) {
503508
this.push(buffer);
504509
};
505510

511+
function fixedToDecimal(value, fraction, typeStr) {
512+
var decimal;
513+
// Convert to decimal object with maximum number of digits 38 and minimum exponent is
514+
// -fraction, so there are at most 'fraction' digits after the decimal
515+
if (util.isString(value)) {
516+
decimal = stringToDecimal(value, maxFixedMantissaLen, -fraction, typeStr);
517+
} else if (util.isNumber(value)) {
518+
decimal = stringToDecimal(value.toExponential(), maxFixedMantissaLen, -fraction, typeStr);
519+
} else {
520+
throw createInputError(typeStr);
521+
}
522+
if (decimal.m.length + decimal.e + fraction > maxFixedMantissaLen) {
523+
throw createInputError(typeStr); // Numeric overflow, greater than maximum precision
524+
}
525+
526+
if ((-decimal.e) < fraction) {
527+
decimal.m += zeropad.ZEROS[fraction + decimal.e];
528+
}
529+
return decimal;
530+
}
531+
532+
function writeFixed16Buffer(decimal, buffer, offset) {
533+
bignum.writeUInt128LE(buffer, decimal.m, offset);
534+
if (decimal.s === -1) {
535+
// Apply two's complement conversion
536+
var extraOne = true;
537+
for (var i = offset; i < offset + 16; i++) {
538+
if (extraOne) {
539+
if (buffer[i] !== 0) {
540+
buffer[i] = 0xff - buffer[i] + 1;
541+
extraOne = false;
542+
} else {
543+
buffer[i] = 0;
544+
}
545+
} else {
546+
buffer[i] = 0xff - buffer[i];
547+
}
548+
}
549+
}
550+
}
551+
552+
function checkFixedOverflow(decimal, extBuffer, byteLimit, typeStr) {
553+
if (decimal.s === -1) {
554+
for (var i = byteLimit; i < 16; ++i) {
555+
if (extBuffer[i] != 0xff) {
556+
throw createInputError(typeStr);
557+
}
558+
}
559+
if ((extBuffer[byteLimit - 1] & 0x80) == 0) {
560+
throw createInputError(typeStr);
561+
}
562+
} else {
563+
for (var i = byteLimit; i < 16; ++i) {
564+
if (extBuffer[i] != 0) {
565+
throw createInputError(typeStr);
566+
}
567+
}
568+
if (extBuffer[byteLimit - 1] & 0x80) {
569+
throw createInputError(typeStr);
570+
}
571+
}
572+
}
573+
574+
Writer.prototype[TypeCode.FIXED8] = function writeFixed8(value, fraction) {
575+
var extBuffer = new Buffer(16);
576+
var decimal = fixedToDecimal(value, fraction, 'FIXED8');
577+
writeFixed16Buffer(decimal, extBuffer, 0);
578+
// Check that the representation does not exceed 8 bytes
579+
checkFixedOverflow(decimal, extBuffer, 8, 'FIXED8');
580+
var buffer = new Buffer(9);
581+
buffer[0] = TypeCode.FIXED8;
582+
extBuffer.copy(buffer, 1, 0, 8);
583+
this.push(buffer);
584+
}
585+
586+
Writer.prototype[TypeCode.FIXED12] = function writeFixed12(value, fraction) {
587+
var extBuffer = new Buffer(16);
588+
var decimal = fixedToDecimal(value, fraction, 'FIXED12');
589+
writeFixed16Buffer(decimal, extBuffer, 0);
590+
// Check that the representation does not exceed 12 bytes
591+
checkFixedOverflow(decimal, extBuffer, 12, 'FIXED12');
592+
var buffer = new Buffer(13);
593+
buffer[0] = TypeCode.FIXED12;
594+
extBuffer.copy(buffer, 1, 0, 12);
595+
this.push(buffer);
596+
}
597+
598+
Writer.prototype[TypeCode.FIXED16] = function writeFixed16(value, fraction) {
599+
var buffer = new Buffer(17);
600+
buffer[0] = TypeCode.FIXED16;
601+
writeFixed16Buffer(fixedToDecimal(value, fraction, 'FIXED16'), buffer, 1);
602+
this.push(buffer);
603+
}
604+
506605
Writer.prototype[TypeCode.NSTRING] = function writeNString(value) {
507606
this.writeCharacters(TypeCode.NSTRING, value);
508607
};
@@ -825,12 +924,12 @@ function trimTrailingZeroes(str) {
825924
return str.substring(0, i + 1);
826925
}
827926

828-
function stringToDecimal(str) {
927+
function stringToDecimal(str, maxMantissaLen, minExp, typeStr) {
829928
/* jshint bitwise:false */
830929
var dec = str.match(REGEX.DECIMAL);
831930
// REGEX.DECIMAL will match "." and "" despite these being invalid.
832931
if (!dec || str === "." || str === "") {
833-
throw createInputError('DECIMAL');
932+
throw createInputError(typeStr === undefined ? 'DECIMAL' : typeStr);
834933
}
835934
var sign = dec[1] === '-' ? -1 : 1;
836935
var mInt = dec[2] || '';
@@ -842,14 +941,27 @@ function stringToDecimal(str) {
842941
if(mantissa.length === 0) mantissa = "0";
843942
exp -= mFrac.length
844943

845-
// round to maxDecimalMantissaLen digits and increment exp appropriately
846-
if(mantissa.length > maxDecimalMantissaLen) {
847-
var followDigit = mantissa[maxDecimalMantissaLen];
848-
exp += (mantissa.length - maxDecimalMantissaLen)
849-
mantissa = mantissa.substring(0, maxDecimalMantissaLen);
944+
var calcMaxMantissaLength = maxMantissaLen;
945+
if (minExp !== undefined && exp < minExp) {
946+
// Shift the maxMantissaLen such that the exponent is minExp
947+
calcMaxMantissaLength = exp + mantissa.length - minExp;
948+
if (calcMaxMantissaLength < 0) {
949+
// All digits are rounded away
950+
return {s: 1, m: "0", e: 0};
951+
} else if (calcMaxMantissaLength == 0) {
952+
// Only the first digit matters
953+
return {s: sign, m: mantissa[0] > '4' ? "1" : "0", e: minExp};
954+
}
955+
}
956+
957+
// round to calcMaxMantissaLen digits and increment exp appropriately
958+
if(mantissa.length > calcMaxMantissaLength) {
959+
var followDigit = mantissa[calcMaxMantissaLength];
960+
exp += (mantissa.length - calcMaxMantissaLength);
961+
mantissa = mantissa.substring(0, calcMaxMantissaLength);
850962
if(followDigit > '4') {
851963
// round up
852-
var i = maxDecimalMantissaLen - 1;
964+
var i = calcMaxMantissaLength - 1;
853965
while(i >= 0 && mantissa[i] === '9') {
854966
i -= 1;
855967
}
@@ -862,9 +974,9 @@ function stringToDecimal(str) {
862974
mantissa = mantissa.substring(0, i + 1);
863975
mantissa = setChar(mantissa, i, String.fromCharCode(mantissa.charCodeAt(i) + 1));
864976
}
865-
} else if(mantissa[maxDecimalMantissaLen - 1] === '0') {
977+
} else if(mantissa[calcMaxMantissaLength - 1] === '0') {
866978
var trimmed = trimTrailingZeroes(mantissa);
867-
exp += (maxDecimalMantissaLen - trimmed.length);
979+
exp += (calcMaxMantissaLength - trimmed.length);
868980
mantissa = trimmed;
869981
}
870982
}

lib/protocol/common/DataFormatVersion.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module.exports = {
2222
LEVEL5: 5,
2323
LEVEL6: 6,
2424
LEVEL7: 7,
25+
LEVEL8: 8,
2526
// Maximum data format version supported by this driver
26-
MAX_VERSION: 7,
27+
MAX_VERSION: 8,
2728
};

lib/protocol/common/NormalizedTypeCode.js

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ NormalizedTypeCode[TypeCode.DOUBLE] = TypeCode.DOUBLE;
3030
NormalizedTypeCode[TypeCode.REAL] = TypeCode.REAL;
3131
// Decimal
3232
NormalizedTypeCode[TypeCode.DECIMAL] = TypeCode.DECIMAL;
33+
// Fixed
34+
NormalizedTypeCode[TypeCode.FIXED8] = TypeCode.FIXED8;
35+
NormalizedTypeCode[TypeCode.FIXED12] = TypeCode.FIXED12;
36+
NormalizedTypeCode[TypeCode.FIXED16] = TypeCode.FIXED16;
3337
// String
3438
NormalizedTypeCode[TypeCode.STRING] = TypeCode.STRING;
3539
NormalizedTypeCode[TypeCode.VARCHAR1] = TypeCode.STRING;

lib/protocol/common/ReadFunction.js

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ var READ_FLOAT = 'readFloat';
3838
var READ_DECIMAL = 'readDecimal';
3939
var READ_ALPHANUM = 'readAlphanum';
4040
var READ_BOOLEAN = 'readBoolean';
41+
var READ_FIXED8 = 'readFixed8';
42+
var READ_FIXED12 = 'readFixed12';
43+
var READ_FIXED16 = 'readFixed16';
4144

4245
ReadFunction[TypeCode.TINYINT] = READ_TINYINT;
4346
ReadFunction[TypeCode.SMALLINT] = READ_SMALLINT;
@@ -75,3 +78,6 @@ ReadFunction[TypeCode.DECIMAL] = READ_DECIMAL;
7578
ReadFunction[TypeCode.ST_GEOMETRY] = READ_BINARY;
7679
ReadFunction[TypeCode.ST_POINT] = READ_BINARY;
7780
ReadFunction[TypeCode.BOOLEAN] = READ_BOOLEAN;
81+
ReadFunction[TypeCode.FIXED8] = READ_FIXED8;
82+
ReadFunction[TypeCode.FIXED12] = READ_FIXED12;
83+
ReadFunction[TypeCode.FIXED16] = READ_FIXED16;

lib/protocol/common/TypeCode.js

+3
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,7 @@ module.exports = {
8686
NCLOB_DISK: 73,
8787
ST_GEOMETRY: 74,
8888
ST_POINT: 75,
89+
FIXED16: 76,
90+
FIXED8: 81,
91+
FIXED12: 82
8992
};

0 commit comments

Comments
 (0)