Skip to content

Commit

Permalink
ENG-12971: nodejs-null-value (#26)
Browse files Browse the repository at this point in the history
Enable writing and reading null values for nodejs driver.
  • Loading branch information
sycai authored and Ning Shi committed Aug 15, 2017
1 parent 1bf846a commit 41e6522
Showing 1 changed file with 158 additions and 54 deletions.
212 changes: 158 additions & 54 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
var BigInteger = require('bignumber').BigInteger,
ctype = require('./ctype'),
endian = 'big';
var BigInteger = require('bignumber').BigInteger,
ctype = require('./ctype'),
endian = 'big';

var NullValueOf = {
byte: -128,
short: -32768,
int: -2147483648,
long: new BigInteger('-9223372036854775808'),
double: -1.7E+308,
decimal: '-170141183460469231731687303715884105728'
};

function Parser(buffer) {
this.buffer = buffer || [];
Expand All @@ -44,30 +53,62 @@ Parser.prototype.writeBinary = function(buffer) {
};

Parser.prototype.readByte = function() {
return ctype.rsint8(this.buffer, endian, this.position++);
var res = ctype.rsint8(this.buffer, endian, this.position++);
if (res === NullValueOf['byte']) {
return null;
} else {
return res;
}
};
Parser.prototype.writeByte = function(value) {
if (value == null) {
value = NullValueOf['byte'];
}
ctype.wsint8(value, endian, this.buffer, this.position++);
};

Parser.prototype.readShort = function() {
return ctype.rsint16(this.buffer, endian, (this.position += 2) - 2);
var res = ctype.rsint16(this.buffer, endian, (this.position += 2) - 2);
if (res === NullValueOf['short']) {
return null;
} else {
return res;
}
};
Parser.prototype.writeShort = function(value) {
if (value == null) {
value = NullValueOf['short'];
}
ctype.wsint16(value, endian, this.buffer, (this.position += 2) - 2);
};

Parser.prototype.readInt = function() {
return ctype.rsint32(this.buffer, endian, (this.position += 4) - 4);
var res = ctype.rsint32(this.buffer, endian, (this.position += 4) - 4);
if (res === NullValueOf['int']) {
return null;
} else {
return res;
}
};
Parser.prototype.writeInt = function(value) {
if (value == null) {
value = NullValueOf['int'];
}
ctype.wsint32(value, endian, this.buffer, (this.position += 4) - 4);
};

Parser.prototype.readDouble = function() {
return ctype.rdouble(this.buffer, endian, (this.position += 8) - 8);
var res = ctype.rdouble(this.buffer, endian, (this.position += 8) - 8);
if (res === NullValueOf['double']) {
return null;
} else {
return res;
}
};
Parser.prototype.writeDouble = function(value) {
if (value == null) {
value = NullValueOf['double'];
}
ctype.wdouble(value, endian, this.buffer, (this.position += 8) - 8);
};

Expand All @@ -81,16 +122,32 @@ Parser.prototype.readLongBytes = function() {
};

Parser.prototype.readLong = function() {
return this.readLongBytes()[0];
var res = this.readLongBytes();
if (res.equals(NullValueOf['long'])) {
return null;
} else {
return res;
}
};
Parser.prototype.writeLong = function(value) {
if (value == null) {
value = NullValueOf['long'];
}
var bytes, numBytes = 8;
if( typeof value === 'number')
value = new BigInteger(value.toString());
if(!( value instanceof BigInteger))
throw new Error('Long type must be a BigInteger or Number');
bytes = value.toByteArray();
bytes = zeros(numBytes - bytes.length).concat(bytes);
if (bytes[0] >= 0) {
while (bytes.length < numBytes) {
bytes.unshift(0);
}
} else {
while (bytes.length < numBytes) {
bytes.unshift(-1);
}
}

for(var i = 0; i < numBytes; i++)
ctype.wsint8(bytes[i], endian, this.buffer, this.position + i);
Expand All @@ -103,32 +160,44 @@ Parser.prototype.readString = function() {
return this.buffer.toString('utf8', this.position, this.position += length);
};
Parser.prototype.writeString = function(value) {
var strBuf = new Buffer(value, 'utf8');
var length = strBuf.length;
var length;
if (value == null) {
length = -1;
} else {
var strBuf = new Buffer(value, 'utf8');
length = strBuf.length;
}
this.writeInt(length);

for(var i = 0, pos = this.position; i < length; i++) {
this.buffer[pos + i] = strBuf[i];
}
this.position += length;
this.position += Math.max(0, length);
};

Parser.prototype.readDate = function(value) {
var bigInt, i = 0, numBytes = 8;
bigInt = this.readLongBytes();
var intStr = bigInt.divide(thousand).toString();
return new Date(parseInt(intStr));
Parser.prototype.readDate = function() {
var bigInt = this.readLongBytes();
if (bigInt.toString() === NullValueOf['long']) {
return null;
} else {
var intStr = bigInt.divide(thousand).toString();
return new Date(parseInt(intStr));
}
};

Parser.prototype.writeDate = function(value) {
var bigInt, i = 0, numBytes = 8;
if( value instanceof Date)
value = Date.getTime();
else if( typeof value !== 'number')
throw new Error('Date type must be a Date or number');
bigInt = new BigInteger(value.toString());

this.writeLong(bigInt.multiply(thousand));
if (value == null) {
this.writeLong(null);
} else {
var bigInt;
if (value instanceof Date)
value = Date.getTime();
else if (typeof value !== 'number')
throw new Error('Date type must be a Date or number');
bigInt = new BigInteger(value.toString());

this.writeLong(bigInt.multiply(thousand));
}
};

Parser.prototype.readDecimal = function() {
Expand All @@ -140,7 +209,7 @@ Parser.prototype.readDecimal = function() {
var val = bigInt.toString();

// handle the null value case
if(val === '-170141183460469231731687303715884105728') {
if(val === NullValueOf['decimal']) {
val = null;
} else if(val.length <= 12) {
// add leading zeros (e.g. 123 to 0.000000000123)
Expand All @@ -149,29 +218,35 @@ Parser.prototype.readDecimal = function() {
} else {
// put the decimal in the right place
val = val.slice(0, -decimalPlaces) + '.' + val.slice(-decimalPlaces);
val = val.replace(/0+$/, '');
// trim the trailing zeros
}
return val;
};
Parser.prototype.writeDecimal = function(value) {
var bytes, bigInt, numBytes = 16, decimalPlaces = 12;
if(value == null)
value = '-170141183460469231731687303715884105728';
if( typeof value === 'number')
value = value.toString();
if( typeof value != 'string' || !/^-?\d*\.?\d*$/.test(value))
throw new Error('Decimal type must be a numerical string or Number:' + value);

// add decimal and missing zeros
var parts = value.split('.');
if(parts.length == 1)
parts.push('000000000000');
else
parts[1] += zeros(12 - parts[1].length).join('');
bigInt = new BigInteger(parts.join(''));
var bytes, bigInt, numBytes = 16;
if(value == null) {
bigInt = new BigInteger(NullValueOf['decimal']);
} else {
if (typeof value === 'number')
value = value.toString();
if (typeof value != 'string' || !(/^-?\d*\.?\d*$/).test(value))
throw new Error('Decimal type must be a numerical string or Number:' + value);

// add decimal and missing zeros
if (value.startsWith('.')) {
value = '0' + value;
}
bigInt = new BigInteger(sanitizeDecimal(value));
}
bytes = bigInt.toByteArray();
bytes = bytes[0] > 0 ? zeros(numBytes - bytes.length).concat(bytes) : ones(numBytes - bytes.length).concat(bytes);
if (bytes[0] >= 0) {
while (bytes.length < numBytes) {
bytes.unshift(0);
}
} else {
while (bytes.length < numBytes) {
bytes.unshift(-1);
}
}

for(var i = 0; i < numBytes; i++)
ctype.wsint8(bytes[i], endian, this.buffer, this.position + i);
Expand All @@ -180,18 +255,22 @@ Parser.prototype.writeDecimal = function(value) {

Parser.prototype.readVarbinary = function() {
var length = this.readInt();
var binary = this.buffer.slice(this.position, this.position + length);
this.position += length;

return binary;
return null;
// BUG: remove
if (length == -1) {
return null;
} else {
var binary = this.buffer.slice(this.position, this.position + length);
this.position += length;
return binary;
}
}

Parser.prototype.writeVarbinary = function(value) {
this.writeInt(value.length);
value.copy(this.buffer, this.position);
this.position += value.length;
if (value == null) {
this.writeInt(-1);
} else {
this.writeInt(value.length);
this.writeBinary(value);
}
}

Parser.prototype.readNull = function() {
Expand Down Expand Up @@ -465,7 +544,7 @@ function checkType(type, value) {
if( typeof value === 'string' && !STRING_TYPES[type])
throw new Error('Providing a string type for a non-string field. ' + value + ' can not be a ' + type);

if( typeof value === 'object' && !( value instanceof Array))
if( typeof value === 'object' && !( value instanceof Array) && !( value instanceof Uint8Array) && (value != null))
throw new Error('Cannot provide custom objects as procedure parameters');

if( value instanceof Array && type.slice(0, 5) != 'array')
Expand All @@ -477,3 +556,28 @@ function checkType(type, value) {
if( value instanceof BigInteger && !BIGINT_TYPES[type])
throw new Error('Providing a BigInteger type for a non-bigint field. ' + value + ' can not be a ' + type);
}

function sanitizeDecimal(value) {
var MAX_INT_DIGIT = 26, MAX_FRAC_DIGIT = 12;

var sign = '';
if (value.startsWith('-')) {
sign = '-';
value = value.slice(1);
}
var parts = value.split('.');
// first check if the given value is legal
if (parts[0].length > MAX_INT_DIGIT) {
throw new Error('The integer part should not have more than' + MAX_INT_DIGIT + 'digits.');
}
if (parts.length == 2 && parts[1].length > MAX_FRAC_DIGIT) {
throw new Error('The fractional part should not have more than' + MAX_FRAC_DIGIT + 'digits.');
}

// add trailing zeros
if (parts.length == 1) {
return sign + parts[0] + zeros(MAX_FRAC_DIGIT).join('');
} else {
return sign + parts[0] + parts[1] + zeros(MAX_FRAC_DIGIT - parts[1].length).join('');
}
}

0 comments on commit 41e6522

Please sign in to comment.