@@ -22,6 +22,7 @@ var LobOptions = common.LobOptions;
2222var NormalizedTypeCode = common . NormalizedTypeCode ;
2323var bignum = util . bignum ;
2424var calendar = util . calendar ;
25+ var zeropad = require ( '../util/zeropad' ) ;
2526var isValidDay = calendar . isValidDay ;
2627var isValidTime = calendar . isValidTime ;
2728var isZeroDay = calendar . isZeroDay ;
@@ -40,12 +41,22 @@ var REGEX = {
4041} ;
4142
4243const maxDecimalMantissaLen = 34 ;
43-
44- function Writer ( types , useCesu8 , spatialTypes ) {
45- this . _types = types . map ( normalizeType ) ;
44+ const maxFixedMantissaLen = 38 ;
45+
46+ /**
47+ * Constructs a Writer to write input parameters into server readable representation
48+ * @param {Object } params - Metadata for input parameters to be written to the server
49+ * @param {number[] } params.types - Array of type codes for each parameter
50+ * @param {number[] } params.fractions - Array of the fraction / scale of each parameter
51+ * (fraction metadata is only necessary for FIXED / DECIMAL types)
52+ * @param {Object } options - Stores options to modify the way data types are written
53+ */
54+ function Writer ( params , options ) {
55+ this . _types = params . types . map ( normalizeType ) ;
56+ this . _fractions = params . fractions ;
4657 this . reset ( ) ;
47- this . _useCesu8 = ( useCesu8 === true ) ;
48- this . _spatialTypes = ( spatialTypes === 1 ? 1 : 0 ) ;
58+ this . _useCesu8 = ( options && options . useCesu8 === true ) ;
59+ this . _spatialTypes = ( ( options && options . spatialTypes === 1 ) ? 1 : 0 ) ;
4960}
5061
5162function normalizeType ( type ) {
@@ -67,13 +78,13 @@ Writer.prototype.reset = function reset() {
6778Writer . prototype . setValues = function setValues ( values ) {
6879 this . reset ( ) ;
6980 for ( var i = 0 ; i < values . length ; i ++ ) {
70- this . add ( this . _types [ i ] , values [ i ] ) ;
81+ this . add ( this . _types [ i ] , values [ i ] , this . _fractions ? this . _fractions [ i ] : undefined ) ;
7182 }
7283 this . _params = true ;
7384} ;
7485
75- exports . create = function createWriter ( params , useCesu8 , spatialTypes ) {
76- var writer = new Writer ( params . types , useCesu8 , spatialTypes ) ;
86+ exports . create = function createWriter ( params , options ) {
87+ var writer = new Writer ( params , options ) ;
7788 writer . setValues ( params . values ) ;
7889 return writer ;
7990} ;
@@ -96,9 +107,12 @@ Object.defineProperties(Writer.prototype, {
96107 }
97108} ) ;
98109
99- Writer . prototype . add = function add ( type , value ) {
110+ Writer . prototype . add = function add ( type , value , fraction ) {
100111 if ( typeof value === 'undefined' || value === null ) {
101112 this . pushNull ( type ) ;
113+ } else if ( type === TypeCode . DECIMAL || type === TypeCode . FIXED8
114+ || type === TypeCode . FIXED12 || type === TypeCode . FIXED16 ) {
115+ this [ type ] ( value , fraction ) ;
102116 } else {
103117 this [ type ] ( value ) ;
104118 }
@@ -533,12 +547,12 @@ Writer.prototype[TypeCode.DOUBLE] = function writeDouble(value) {
533547 this . push ( buffer ) ;
534548} ;
535549
536- Writer . prototype [ TypeCode . DECIMAL ] = function writeDecimal ( value ) {
550+ Writer . prototype [ TypeCode . DECIMAL ] = function writeDecimal ( value , fraction ) {
537551 var decimal ;
538552 if ( util . isString ( value ) ) {
539- decimal = stringToDecimal ( value ) ;
553+ decimal = stringToDecimal ( value , maxDecimalMantissaLen , fraction ) ;
540554 } else if ( util . isNumber ( value ) ) {
541- decimal = stringToDecimal ( value . toExponential ( ) ) ;
555+ decimal = stringToDecimal ( value . toExponential ( ) , maxDecimalMantissaLen , fraction ) ;
542556 } else {
543557 throw createInputError ( 'DECIMAL' ) ;
544558 }
@@ -548,6 +562,103 @@ Writer.prototype[TypeCode.DECIMAL] = function writeDecimal(value) {
548562 this . push ( buffer ) ;
549563} ;
550564
565+ function toFixedDecimal ( value , fraction , typeStr ) {
566+ var decimal ;
567+ // Convert to decimal object with maximum number of digits 38
568+ if ( util . isString ( value ) ) {
569+ decimal = stringToDecimal ( value , maxFixedMantissaLen , fraction , typeStr ) ;
570+ } else if ( util . isNumber ( value ) ) {
571+ decimal = stringToDecimal ( value . toExponential ( ) , maxFixedMantissaLen , fraction , typeStr ) ;
572+ } else {
573+ throw createInputError ( typeStr ) ;
574+ }
575+ // Truncate decimal with the minimum exponent being -fraction, so there are at most
576+ // 'fraction' digits after the decimal
577+ decimal = truncateDecimalToExp ( decimal , - fraction ) ;
578+
579+ if ( decimal . m . length + decimal . e + fraction > maxFixedMantissaLen ) {
580+ throw createInputError ( typeStr ) ; // Numeric overflow, greater than maximum precision
581+ }
582+
583+ if ( ( - decimal . e ) < fraction ) {
584+ decimal . m += zeropad . ZEROS [ fraction + decimal . e ] ;
585+ }
586+ return decimal ;
587+ }
588+
589+ function writeFixed16Buffer ( decimal , buffer , offset ) {
590+ bignum . writeUInt128LE ( buffer , decimal . m , offset ) ;
591+ if ( decimal . s === - 1 ) {
592+ // Apply two's complement conversion
593+ var extraOne = true ;
594+ for ( var i = offset ; i < offset + 16 ; i ++ ) {
595+ if ( extraOne ) {
596+ if ( buffer [ i ] !== 0 ) {
597+ buffer [ i ] = 0xff - buffer [ i ] + 1 ;
598+ extraOne = false ;
599+ } else {
600+ buffer [ i ] = 0 ;
601+ }
602+ } else {
603+ buffer [ i ] = 0xff - buffer [ i ] ;
604+ }
605+ }
606+ }
607+ }
608+
609+ function checkFixedOverflow ( decimal , extBuffer , byteLimit , typeStr ) {
610+ if ( decimal . s === - 1 ) {
611+ for ( var i = byteLimit ; i < 16 ; ++ i ) {
612+ if ( extBuffer [ i ] != 0xff ) {
613+ throw createInputError ( typeStr ) ;
614+ }
615+ }
616+ if ( ( extBuffer [ byteLimit - 1 ] & 0x80 ) == 0 ) {
617+ throw createInputError ( typeStr ) ;
618+ }
619+ } else {
620+ for ( var i = byteLimit ; i < 16 ; ++ i ) {
621+ if ( extBuffer [ i ] != 0 ) {
622+ throw createInputError ( typeStr ) ;
623+ }
624+ }
625+ if ( extBuffer [ byteLimit - 1 ] & 0x80 ) {
626+ throw createInputError ( typeStr ) ;
627+ }
628+ }
629+ }
630+
631+ Writer . prototype [ TypeCode . FIXED8 ] = function writeFixed8 ( value , fraction ) {
632+ var extBuffer = new Buffer ( 16 ) ;
633+ var decimal = toFixedDecimal ( value , fraction , 'FIXED8' ) ;
634+ writeFixed16Buffer ( decimal , extBuffer , 0 ) ;
635+ // Check that the representation does not exceed 8 bytes
636+ checkFixedOverflow ( decimal , extBuffer , 8 , 'FIXED8' ) ;
637+ var buffer = new Buffer ( 9 ) ;
638+ buffer [ 0 ] = TypeCode . FIXED8 ;
639+ extBuffer . copy ( buffer , 1 , 0 , 8 ) ;
640+ this . push ( buffer ) ;
641+ }
642+
643+ Writer . prototype [ TypeCode . FIXED12 ] = function writeFixed12 ( value , fraction ) {
644+ var extBuffer = new Buffer ( 16 ) ;
645+ var decimal = toFixedDecimal ( value , fraction , 'FIXED12' ) ;
646+ writeFixed16Buffer ( decimal , extBuffer , 0 ) ;
647+ // Check that the representation does not exceed 12 bytes
648+ checkFixedOverflow ( decimal , extBuffer , 12 , 'FIXED12' ) ;
649+ var buffer = new Buffer ( 13 ) ;
650+ buffer [ 0 ] = TypeCode . FIXED12 ;
651+ extBuffer . copy ( buffer , 1 , 0 , 12 ) ;
652+ this . push ( buffer ) ;
653+ }
654+
655+ Writer . prototype [ TypeCode . FIXED16 ] = function writeFixed16 ( value , fraction ) {
656+ var buffer = new Buffer ( 17 ) ;
657+ buffer [ 0 ] = TypeCode . FIXED16 ;
658+ writeFixed16Buffer ( toFixedDecimal ( value , fraction , 'FIXED16' ) , buffer , 1 ) ;
659+ this . push ( buffer ) ;
660+ }
661+
551662Writer . prototype [ TypeCode . NSTRING ] = function writeNString ( value ) {
552663 this . writeCharacters ( TypeCode . NSTRING , value ) ;
553664} ;
@@ -870,12 +981,12 @@ function trimTrailingZeroes(str) {
870981 return str . substring ( 0 , i + 1 ) ;
871982}
872983
873- function stringToDecimal ( str ) {
984+ function stringToDecimal ( str , maxMantissaLen , fraction , typeStr ) {
874985 /* jshint bitwise:false */
875986 var dec = str . match ( REGEX . DECIMAL ) ;
876987 // REGEX.DECIMAL will match "." and "" despite these being invalid.
877988 if ( ! dec || str === "." || str === "" ) {
878- throw createInputError ( 'DECIMAL' ) ;
989+ throw createInputError ( typeStr === undefined ? 'DECIMAL' : typeStr ) ;
879990 }
880991 var sign = dec [ 1 ] === '-' ? - 1 : 1 ;
881992 var mInt = dec [ 2 ] || '' ;
@@ -887,14 +998,17 @@ function stringToDecimal(str) {
887998 if ( mantissa . length === 0 ) mantissa = "0" ;
888999 exp -= mFrac . length
8891000
890- // round to maxDecimalMantissaLen digits and increment exp appropriately
891- if ( mantissa . length > maxDecimalMantissaLen ) {
892- var followDigit = mantissa [ maxDecimalMantissaLen ] ;
893- exp += ( mantissa . length - maxDecimalMantissaLen )
894- mantissa = mantissa . substring ( 0 , maxDecimalMantissaLen ) ;
895- if ( followDigit > '4' ) {
1001+ // Fit to maxMantissaLen digits and increment exp appropriately
1002+ if ( mantissa . length > maxMantissaLen ) {
1003+ var followDigit = mantissa [ maxMantissaLen ] ;
1004+ exp += ( mantissa . length - maxMantissaLen )
1005+ mantissa = mantissa . substring ( 0 , maxMantissaLen ) ;
1006+ // When writing a floating point decimal (fraction > maxMantissaLen or
1007+ // fraction === undefined), we round to the max mantissa size, but with
1008+ // fixed we truncate to the max mantissa size
1009+ if ( ( fraction === undefined || fraction > maxMantissaLen ) && followDigit > '4' ) {
8961010 // round up
897- var i = maxDecimalMantissaLen - 1 ;
1011+ var i = maxMantissaLen - 1 ;
8981012 while ( i >= 0 && mantissa [ i ] === '9' ) {
8991013 i -= 1 ;
9001014 }
@@ -907,9 +1021,9 @@ function stringToDecimal(str) {
9071021 mantissa = mantissa . substring ( 0 , i + 1 ) ;
9081022 mantissa = setChar ( mantissa , i , String . fromCharCode ( mantissa . charCodeAt ( i ) + 1 ) ) ;
9091023 }
910- } else if ( mantissa [ maxDecimalMantissaLen - 1 ] === '0' ) {
1024+ } else if ( mantissa [ maxMantissaLen - 1 ] === '0' ) {
9111025 var trimmed = trimTrailingZeroes ( mantissa ) ;
912- exp += ( maxDecimalMantissaLen - trimmed . length ) ;
1026+ exp += ( maxMantissaLen - trimmed . length ) ;
9131027 mantissa = trimmed ;
9141028 }
9151029 }
@@ -921,6 +1035,34 @@ function stringToDecimal(str) {
9211035 } ;
9221036}
9231037
1038+ function truncateDecimalToExp ( decimal , minExp ) {
1039+ var mantissa = decimal . m ;
1040+ var exp = decimal . e ;
1041+ var calcMaxMantissaLength ;
1042+ if ( exp < minExp ) {
1043+ // Shift the max mantissa length such that the exponent is minExp
1044+ calcMaxMantissaLength = exp + mantissa . length - minExp ;
1045+ if ( calcMaxMantissaLength <= 0 ) {
1046+ // All digits are truncated away
1047+ return { s : 1 , m : "0" , e : minExp } ;
1048+ }
1049+ }
1050+
1051+ // truncate to calcMaxMantissaLen digits and increment exp appropriately
1052+ if ( calcMaxMantissaLength && mantissa . length > calcMaxMantissaLength ) {
1053+ exp += ( mantissa . length - calcMaxMantissaLength ) ;
1054+ mantissa = mantissa . substring ( 0 , calcMaxMantissaLength ) ;
1055+ // No need to trim trailing zeros since FIXED types will add zeros to
1056+ // match minExp
1057+ }
1058+
1059+ return {
1060+ s : decimal . s ,
1061+ m : mantissa ,
1062+ e : exp
1063+ } ;
1064+ }
1065+
9241066function createInputError ( type ) {
9251067 return new Error ( util . format ( 'Wrong input for %s type' , type ) ) ;
9261068}
0 commit comments