tedious
Version:
A TDS driver, for connecting to MS SQLServer databases.
344 lines (294 loc) • 9.37 kB
JavaScript
"use strict";
const Transform = require('readable-stream').Transform;
const TYPE = require('./token').TYPE;
const tokenParsers = {};
tokenParsers[TYPE.COLMETADATA] = require('./colmetadata-token-parser');
tokenParsers[TYPE.DONE] = require('./done-token-parser').doneParser;
tokenParsers[TYPE.DONEINPROC] = require('./done-token-parser').doneInProcParser;
tokenParsers[TYPE.DONEPROC] = require('./done-token-parser').doneProcParser;
tokenParsers[TYPE.ENVCHANGE] = require('./env-change-token-parser');
tokenParsers[TYPE.ERROR] = require('./infoerror-token-parser').errorParser;
tokenParsers[TYPE.FEDAUTHINFO] = require('./fedauth-info-parser');
tokenParsers[TYPE.FEATUREEXTACK] = require('./feature-ext-ack-parser');
tokenParsers[TYPE.INFO] = require('./infoerror-token-parser').infoParser;
tokenParsers[TYPE.LOGINACK] = require('./loginack-token-parser');
tokenParsers[TYPE.ORDER] = require('./order-token-parser');
tokenParsers[TYPE.RETURNSTATUS] = require('./returnstatus-token-parser');
tokenParsers[TYPE.RETURNVALUE] = require('./returnvalue-token-parser');
tokenParsers[TYPE.ROW] = require('./row-token-parser');
tokenParsers[TYPE.NBCROW] = require('./nbcrow-token-parser');
tokenParsers[TYPE.SSPI] = require('./sspi-token-parser');
module.exports = class Parser extends Transform {
constructor(debug, colMetadata, options) {
super({
objectMode: true
});
this.debug = debug;
this.colMetadata = colMetadata;
this.options = options;
this.endOfMessageMarker = {};
this.buffer = Buffer.alloc(0);
this.position = 0;
this.suspended = false;
this.next = undefined;
}
_transform(input, encoding, done) {
if (input === this.endOfMessageMarker) {
done(null, {
// generate endOfMessage pseudo token
name: 'EOM',
event: 'endOfMessage'
});
return;
}
if (this.position === this.buffer.length) {
this.buffer = input;
} else {
this.buffer = Buffer.concat([this.buffer.slice(this.position), input]);
}
this.position = 0;
if (this.suspended) {
// Unsuspend and continue from where ever we left off.
this.suspended = false;
this.next.call(null);
} // If we're no longer suspended, parse new tokens
if (!this.suspended) {
// Start the parser
this.parseTokens();
}
done();
}
parseTokens() {
const doneParsing = token => {
if (token) {
switch (token.name) {
case 'COLMETADATA':
this.colMetadata = token.columns;
}
this.push(token);
}
};
while (!this.suspended && this.position + 1 <= this.buffer.length) {
const type = this.buffer.readUInt8(this.position);
this.position += 1;
if (tokenParsers[type]) {
tokenParsers[type](this, this.colMetadata, this.options, doneParsing);
} else {
this.emit('error', new Error('Unknown type: ' + type));
}
}
}
suspend(next) {
this.suspended = true;
this.next = next;
}
awaitData(length, callback) {
if (this.position + length <= this.buffer.length) {
callback();
} else {
this.suspend(() => {
this.awaitData(length, callback);
});
}
}
readInt8(callback) {
this.awaitData(1, () => {
const data = this.buffer.readInt8(this.position);
this.position += 1;
callback(data);
});
}
readUInt8(callback) {
this.awaitData(1, () => {
const data = this.buffer.readUInt8(this.position);
this.position += 1;
callback(data);
});
}
readInt16LE(callback) {
this.awaitData(2, () => {
const data = this.buffer.readInt16LE(this.position);
this.position += 2;
callback(data);
});
}
readInt16BE(callback) {
this.awaitData(2, () => {
const data = this.buffer.readInt16BE(this.position);
this.position += 2;
callback(data);
});
}
readUInt16LE(callback) {
this.awaitData(2, () => {
const data = this.buffer.readUInt16LE(this.position);
this.position += 2;
callback(data);
});
}
readUInt16BE(callback) {
this.awaitData(2, () => {
const data = this.buffer.readUInt16BE(this.position);
this.position += 2;
callback(data);
});
}
readInt32LE(callback) {
this.awaitData(4, () => {
const data = this.buffer.readInt32LE(this.position);
this.position += 4;
callback(data);
});
}
readInt32BE(callback) {
this.awaitData(4, () => {
const data = this.buffer.readInt32BE(this.position);
this.position += 4;
callback(data);
});
}
readUInt32LE(callback) {
this.awaitData(4, () => {
const data = this.buffer.readUInt32LE(this.position);
this.position += 4;
callback(data);
});
}
readUInt32BE(callback) {
this.awaitData(4, () => {
const data = this.buffer.readUInt32BE(this.position);
this.position += 4;
callback(data);
});
}
readInt64LE(callback) {
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);
this.position += 8;
callback(data);
});
}
readInt64BE(callback) {
this.awaitData(8, () => {
const data = Math.pow(2, 32) * this.buffer.readInt32BE(this.position) + ((this.buffer[this.position] & 0x80) === 0x80 ? 1 : -1) * this.buffer.readUInt32BE(this.position + 4);
this.position += 8;
callback(data);
});
}
readUInt64LE(callback) {
this.awaitData(8, () => {
const data = Math.pow(2, 32) * this.buffer.readUInt32LE(this.position + 4) + this.buffer.readUInt32LE(this.position);
this.position += 8;
callback(data);
});
}
readUInt64BE(callback) {
this.awaitData(8, () => {
const data = Math.pow(2, 32) * this.buffer.readUInt32BE(this.position) + this.buffer.readUInt32BE(this.position + 4);
this.position += 8;
callback(data);
});
}
readFloatLE(callback) {
this.awaitData(4, () => {
const data = this.buffer.readFloatLE(this.position);
this.position += 4;
callback(data);
});
}
readFloatBE(callback) {
this.awaitData(4, () => {
const data = this.buffer.readFloatBE(this.position);
this.position += 4;
callback(data);
});
}
readDoubleLE(callback) {
this.awaitData(8, () => {
const data = this.buffer.readDoubleLE(this.position);
this.position += 8;
callback(data);
});
}
readDoubleBE(callback) {
this.awaitData(8, () => {
const data = this.buffer.readDoubleBE(this.position);
this.position += 8;
callback(data);
});
}
readUInt24LE(callback) {
this.awaitData(3, () => {
const low = this.buffer.readUInt16LE(this.position);
const high = this.buffer.readUInt8(this.position + 2);
this.position += 3;
callback(low | high << 16);
});
}
readUInt40LE(callback) {
this.awaitData(5, () => {
const low = this.buffer.readUInt32LE(this.position);
const high = this.buffer.readUInt8(this.position + 4);
this.position += 5;
callback(0x100000000 * high + low);
});
}
readUNumeric64LE(callback) {
this.awaitData(8, () => {
const low = this.buffer.readUInt32LE(this.position);
const high = this.buffer.readUInt32LE(this.position + 4);
this.position += 8;
callback(0x100000000 * high + low);
});
}
readUNumeric96LE(callback) {
this.awaitData(12, () => {
const dword1 = this.buffer.readUInt32LE(this.position);
const dword2 = this.buffer.readUInt32LE(this.position + 4);
const dword3 = this.buffer.readUInt32LE(this.position + 8);
this.position += 12;
callback(dword1 + 0x100000000 * dword2 + 0x100000000 * 0x100000000 * dword3);
});
}
readUNumeric128LE(callback) {
this.awaitData(16, () => {
const dword1 = this.buffer.readUInt32LE(this.position);
const dword2 = this.buffer.readUInt32LE(this.position + 4);
const dword3 = this.buffer.readUInt32LE(this.position + 8);
const dword4 = this.buffer.readUInt32LE(this.position + 12);
this.position += 16;
callback(dword1 + 0x100000000 * dword2 + 0x100000000 * 0x100000000 * dword3 + 0x100000000 * 0x100000000 * 0x100000000 * dword4);
});
} // Variable length data
readBuffer(length, callback) {
this.awaitData(length, () => {
const data = this.buffer.slice(this.position, this.position + length);
this.position += length;
callback(data);
});
} // Read a Unicode String (BVARCHAR)
readBVarChar(callback) {
this.readUInt8(length => {
this.readBuffer(length * 2, data => {
callback(data.toString('ucs2'));
});
});
} // Read a Unicode String (USVARCHAR)
readUsVarChar(callback) {
this.readUInt16LE(length => {
this.readBuffer(length * 2, data => {
callback(data.toString('ucs2'));
});
});
} // Read binary data (BVARBYTE)
readBVarByte(callback) {
this.readUInt8(length => {
this.readBuffer(length, callback);
});
} // Read binary data (USVARBYTE)
readUsVarByte(callback) {
this.readUInt16LE(length => {
this.readBuffer(length, callback);
});
}
};