Skip to content

Commit

Permalink
fix: switch to jsbi for reading and writing int64 and uint64 va…
Browse files Browse the repository at this point in the history
…lues
  • Loading branch information
arthurschreiber committed Oct 2, 2019
1 parent 717eb95 commit cf2a759
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 264 deletions.
72 changes: 72 additions & 0 deletions src/token/done-token-parser.js
@@ -0,0 +1,72 @@
// s2.2.7.5/6/7
const JSBI = require('jsbi');

const STATUS = {
MORE: 0x0001,
ERROR: 0x0002,
// This bit is not yet in use by SQL Server, so is not exposed in the returned token
INXACT: 0x0004,
COUNT: 0x0010,
ATTN: 0x0020,
SRVERROR: 0x0100
};

function parseToken(parser, options, callback) {
parser.readUInt16LE((status) => {
const more = !!(status & STATUS.MORE);
const sqlError = !!(status & STATUS.ERROR);
const rowCountValid = !!(status & STATUS.COUNT);
const attention = !!(status & STATUS.ATTN);
const serverError = !!(status & STATUS.SRVERROR);

parser.readUInt16LE((curCmd) => {
const next = (rowCount) => {
callback({
name: 'DONE',
event: 'done',
more: more,
sqlError: sqlError,
attention: attention,
serverError: serverError,
rowCount: rowCountValid ? rowCount : undefined,
curCmd: curCmd
});
};

if (options.tdsVersion < '7_2') {
parser.readUInt32LE(next);
} else {
parser.readBigUInt64LE((rowCount) => {
next(JSBI.toNumber(rowCount));
});
}
});
});
}

module.exports.doneParser = doneParser;
function doneParser(parser, colMetadata, options, callback) {
parseToken(parser, options, (token) => {
token.name = 'DONE';
token.event = 'done';
callback(token);
});
}

module.exports.doneInProcParser = doneInProcParser;
function doneInProcParser(parser, colMetadata, options, callback) {
parseToken(parser, options, (token) => {
token.name = 'DONEINPROC';
token.event = 'doneInProc';
callback(token);
});
}

module.exports.doneProcParser = doneProcParser;
function doneProcParser(parser, colMetadata, options, callback) {
parseToken(parser, options, (token) => {
token.name = 'DONEPROC';
token.event = 'doneProc';
callback(token);
});
}
38 changes: 38 additions & 0 deletions src/token/stream-parser.ts
@@ -1,5 +1,6 @@
import Debug from '../debug';
import { InternalConnectionOptions } from '../connection';
import JSBI from 'jsbi';

const Transform = require('readable-stream').Transform;
import { TYPE, Token, EndOfMessageToken, ColMetadataToken } from './token';
Expand Down Expand Up @@ -212,6 +213,32 @@ class Parser extends Transform {
});
}

readBigInt64LE(callback: (data: JSBI) => void) {
this.awaitData(8, () => {
const result = JSBI.add(
JSBI.leftShift(
JSBI.BigInt(
this.buffer[this.position + 4] +
this.buffer[this.position + 5] * 2 ** 8 +
this.buffer[this.position + 6] * 2 ** 16 +
(this.buffer[this.position + 7] << 24) // Overflow
),
JSBI.BigInt(32)
),
JSBI.BigInt(
this.buffer[this.position] +
this.buffer[this.position + 1] * 2 ** 8 +
this.buffer[this.position + 2] * 2 ** 16 +
this.buffer[this.position + 3] * 2 ** 24
)
);

this.position += 8;

callback(result);
});
}

readInt64LE(callback: (data: number) => void) {
this.awaitData(8, () => {
const data = Math.pow(2, 32) * this.buffer.readInt32LE(this.position + 4) + ((this.buffer[this.position + 4] & 0x80) === 0x80 ? 1 : -1) * this.buffer.readUInt32LE(this.position);
Expand All @@ -228,6 +255,17 @@ class Parser extends Transform {
});
}

readBigUInt64LE(callback: (data: JSBI) => void) {
this.awaitData(8, () => {
const low = JSBI.BigInt(this.buffer.readUInt32LE(this.position));
const high = JSBI.BigInt(this.buffer.readUInt32LE(this.position + 4));

this.position += 8;

callback(JSBI.add(low, JSBI.leftShift(high, JSBI.BigInt(32))));
});
}

readUInt64LE(callback: (data: number) => void) {
this.awaitData(8, () => {
const data = Math.pow(2, 32) * this.buffer.readUInt32LE(this.position + 4) + this.buffer.readUInt32LE(this.position);
Expand Down
83 changes: 0 additions & 83 deletions src/tracking-buffer/bigint.ts

This file was deleted.

40 changes: 35 additions & 5 deletions src/tracking-buffer/writable-tracking-buffer.ts
@@ -1,4 +1,4 @@
import { numberToInt64LE } from './bigint';
import JSBI from 'jsbi';

const SHIFT_LEFT_32 = (1 << 16) * (1 << 16);
const SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
Expand Down Expand Up @@ -106,9 +106,36 @@ class WritableTrackingBuffer {
this.position += length;
}

writeBigInt64LE(value: JSBI) {
this.writeBigU_Int64LE(value);
}

private writeBigU_Int64LE(value: JSBI) {
this.makeRoomFor(8);

let lo = JSBI.toNumber(JSBI.bitwiseAnd(value, JSBI.BigInt(0xffffffff)));

this.buffer[this.position++] = lo;
lo = lo >> 8;
this.buffer[this.position++] = lo;
lo = lo >> 8;
this.buffer[this.position++] = lo;
lo = lo >> 8;
this.buffer[this.position++] = lo;

let hi = JSBI.toNumber(JSBI.bitwiseAnd(JSBI.signedRightShift(value, JSBI.BigInt(32)), JSBI.BigInt(0xffffffff)));

this.buffer[this.position++] = hi;
hi = hi >> 8;
this.buffer[this.position++] = hi;
hi = hi >> 8;
this.buffer[this.position++] = hi;
hi = hi >> 8;
this.buffer[this.position++] = hi;
}

writeInt64LE(value: number) {
const buf = numberToInt64LE(value);
this.copyFrom(buf);
this.writeBigInt64LE(JSBI.BigInt(value));
}

writeUInt32BE(value: number) {
Expand All @@ -125,8 +152,11 @@ class WritableTrackingBuffer {
}

writeUInt64LE(value: number) {
this.writeInt32LE(value & -1);
this.writeUInt32LE(Math.floor(value * SHIFT_RIGHT_32));
this.writeBigUInt64LE(JSBI.BigInt(value));
}

writeBigUInt64LE(value: JSBI) {
this.writeBigU_Int64LE(value);
}

writeInt8(value: number) {
Expand Down
9 changes: 4 additions & 5 deletions src/value-parser.js
Expand Up @@ -6,7 +6,6 @@ const guidParser = require('./guid-parser');
const readPrecision = require('./metadata-parser').readPrecision;
const readScale = require('./metadata-parser').readScale;
const readCollation = require('./metadata-parser').readCollation;
const convertLEBytesToString = require('./tracking-buffer/bigint').convertLEBytesToString;

const NULL = (1 << 16) - 1;
const MAX = (1 << 16) - 1;
Expand Down Expand Up @@ -87,8 +86,8 @@ function valueParse(parser, metaData, options, callback) {
return parser.readInt16LE(callback);

case 'BigInt':
return parser.readBuffer(8, (buffer) => {
callback(convertLEBytesToString(buffer));
return parser.readBigInt64LE((value) => {
callback(value.toString());
});

case 'IntN':
Expand All @@ -103,8 +102,8 @@ function valueParse(parser, metaData, options, callback) {
case 4:
return parser.readInt32LE(callback);
case 8:
return parser.readBuffer(8, (buffer) => {
callback(convertLEBytesToString(buffer));
return parser.readBigInt64LE((value) => {
callback(value.toString());
});

default:
Expand Down
4 changes: 4 additions & 0 deletions test/integration/datatypes-in-results-test.js
Expand Up @@ -127,6 +127,10 @@ describe('Datatypes in results test', function() {
execSql(done, 'select cast(8 as bigint)', '8');
});

it('should test negative big int', function(done) {
execSql(done, 'select cast(-8 as bigint)', '-8');
});

it('should test big int null', function(done) {
execSql(done, 'select cast(null as bigint)', null);
});
Expand Down
12 changes: 12 additions & 0 deletions test/integration/rpc-test.js
Expand Up @@ -201,6 +201,18 @@ describe('RPC test', function() {
testProc(done, TYPES.SmallInt, 'smallint', null);
});

it('should exec proc bigint', function(done) {
testProc(done, TYPES.BigInt, 'bigint', '3');
});

it('should exec proc negative bigint', function(done) {
testProc(done, TYPES.BigInt, 'bigint', '-3');
});

it('should exec proc bigint null', function(done) {
testProc(done, TYPES.BigInt, 'bigint', null);
});

it('should exec proc int', function(done) {
testProc(done, TYPES.Int, 'int', 3);
});
Expand Down

0 comments on commit cf2a759

Please sign in to comment.