Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
400 changes: 146 additions & 254 deletions src/metadata-parser.ts

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions src/token/buffer-reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Parser from './stream-parser';

class NotEnoughDataError extends Error { }


export default class BufferReader {
private offset: number;
parser: Parser;

constructor(parser: Parser) {
this.offset = parser.position;
this.parser = parser;
}

private checkDataLength(buffer: Buffer, numBytes: number): void {
if (buffer.length < this.parser.position + numBytes) {
this.parser.position = this.offset;
throw new NotEnoughDataError();
}
}

readUInt8(): number {
const numBytes = 1;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt8(this.parser.position);
this.parser.position += numBytes;
return data;
}

readUInt16LE(): number {
const numBytes = 2;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt16LE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readUInt32LE(): number {
const numBytes = 4;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt32LE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readUInt32BE(): number {
const numBytes = 4;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readUInt32BE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readInt32LE(): number {
const numBytes = 4;
this.checkDataLength(this.parser.buffer, numBytes);
const data = this.parser.buffer.readInt32LE(this.parser.position);
this.parser.position += numBytes;
return data;
}

readBVarChar(): string {
const numBytes = this.readUInt8() * 2;
const data = this.readFromBuffer(numBytes).toString('ucs2');
return data;
}


readUsVarChar(): string {
const numBytes = this.readUInt16LE() * 2;
const data = this.readFromBuffer(numBytes).toString('ucs2');
return data;
}

readFromBuffer(numBytes: number): Buffer {
this.checkDataLength(this.parser.buffer, numBytes);
const result = this.parser.buffer.slice(this.parser.position, this.parser.position + numBytes);
this.parser.position += numBytes;
return result;
}

}

module.exports = BufferReader;
42 changes: 26 additions & 16 deletions src/token/colmetadata-token-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface ColumnMetadata extends Metadata {
tableName?: string | string[] | undefined;
}

class NotEnoughDataError extends Error { }

function readTableName(parser: Parser, options: ParserOptions, metadata: Metadata, callback: (tableName?: string | string[]) => void) {
if (metadata.type.hasTableName) {
if (options.tdsVersion >= '7_2') {
Expand Down Expand Up @@ -60,22 +62,30 @@ function readColumnName(parser: Parser, options: ParserOptions, index: number, m
}

function readColumn(parser: Parser, options: ParserOptions, index: number, callback: (column: ColumnMetadata) => void) {
metadataParse(parser, options, (metadata) => {
readTableName(parser, options, metadata, (tableName) => {
readColumnName(parser, options, index, metadata, (colName) => {
callback({
userType: metadata.userType,
flags: metadata.flags,
type: metadata.type,
collation: metadata.collation,
precision: metadata.precision,
scale: metadata.scale,
udtInfo: metadata.udtInfo,
dataLength: metadata.dataLength,
schema: metadata.schema,
colName: colName,
tableName: tableName
});
let metadata!: Metadata;
try {
metadata = metadataParse(parser, options);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
readColumn(parser, options, index, callback);
});
}
}
readTableName(parser, options, metadata, (tableName) => {
readColumnName(parser, options, index, metadata, (colName) => {
callback({
userType: metadata.userType,
flags: metadata.flags,
type: metadata.type,
collation: metadata.collation,
precision: metadata.precision,
scale: metadata.scale,
udtInfo: metadata.udtInfo,
dataLength: metadata.dataLength,
schema: metadata.schema,
colName: colName,
tableName: tableName
});
});
});
Expand Down
79 changes: 46 additions & 33 deletions src/token/infoerror-token-parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Parser, { ParserOptions } from './stream-parser';

import BufferReader from './buffer-reader';
import { InfoMessageToken, ErrorMessageToken } from './token';

class NotEnoughDataError extends Error { }

interface TokenData {
number: number;
state: number;
Expand All @@ -12,43 +14,54 @@ interface TokenData {
lineNumber: number;
}

function parseToken(parser: Parser, options: ParserOptions, callback: (data: TokenData) => void) {
function parseToken(parser: Parser, options: ParserOptions): TokenData {
// length
parser.readUInt16LE(() => {
parser.readUInt32LE((number) => {
parser.readUInt8((state) => {
parser.readUInt8((clazz) => {
parser.readUsVarChar((message) => {
parser.readBVarChar((serverName) => {
parser.readBVarChar((procName) => {
(options.tdsVersion < '7_2' ? parser.readUInt16LE : parser.readUInt32LE).call(parser, (lineNumber: number) => {
callback({
'number': number,
'state': state,
'class': clazz,
'message': message,
'serverName': serverName,
'procName': procName,
'lineNumber': lineNumber
});
});
});
});
});
});
});
});
});
const br = new BufferReader(parser);
br.readUInt16LE();
const number = br.readUInt32LE();
const state = br.readUInt8();
const clazz = br.readUInt8();
const message = br.readUsVarChar();
const serverName = br.readBVarChar();
const procName = br.readBVarChar();
const lineNumber = options.tdsVersion < '7_2' ? br.readUInt16LE() : br.readUInt32LE();
return {
'number': number,
'state': state,
'class': clazz,
'message': message,
'serverName': serverName,
'procName': procName,
'lineNumber': lineNumber
} as TokenData;
}

export function infoParser(parser: Parser, options: ParserOptions, callback: (token: InfoMessageToken) => void) {
parseToken(parser, options, (data) => {
callback(new InfoMessageToken(data));
});
let data!: TokenData;
try {
data = parseToken(parser, options);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
infoParser(parser, options, callback);
});
}
}

callback(new InfoMessageToken(data));
}

export function errorParser(parser: Parser, options: ParserOptions, callback: (token: ErrorMessageToken) => void) {
parseToken(parser, options, (data) => {
callback(new ErrorMessageToken(data));
});
let data!: TokenData;
try {
data = parseToken(parser, options);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
errorParser(parser, options, callback);
});
}
}

callback(new ErrorMessageToken(data));
}
69 changes: 40 additions & 29 deletions src/token/loginack-token-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,54 @@ import Parser, { ParserOptions } from './stream-parser';
import { LoginAckToken } from './token';

import { versionsByValue as versions } from '../tds-versions';
import BufferReader from './buffer-reader';

class NotEnoughDataError extends Error { }

const interfaceTypes: { [key: number]: string } = {
0: 'SQL_DFLT',
1: 'SQL_TSQL'
};

function parseToken(parser: Parser): LoginAckToken {
const br = new BufferReader(parser);
br.readUInt16LE();
const interfaceNumber = br.readUInt8();
const interfaceType = interfaceTypes[interfaceNumber];
const tdsVersionNumber = br.readUInt32BE();
const tdsVersion = versions[tdsVersionNumber];
const progName = br.readBVarChar();
const major = br.readUInt8();
const minor = br.readUInt8();
const buildNumHi = br.readUInt8();
const buildNumLow = br.readUInt8();

return new LoginAckToken({
interface: interfaceType,
tdsVersion: tdsVersion,
progName: progName,
progVersion: {
major: major,
minor: minor,
buildNumHi: buildNumHi,
buildNumLow: buildNumLow
}
});
}

function loginAckParser(parser: Parser, _options: ParserOptions, callback: (token: LoginAckToken) => void) {
// length
parser.readUInt16LE(() => {
parser.readUInt8((interfaceNumber) => {
const interfaceType = interfaceTypes[interfaceNumber];
parser.readUInt32BE((tdsVersionNumber) => {
const tdsVersion = versions[tdsVersionNumber];
parser.readBVarChar((progName) => {
parser.readUInt8((major) => {
parser.readUInt8((minor) => {
parser.readUInt8((buildNumHi) => {
parser.readUInt8((buildNumLow) => {
callback(new LoginAckToken({
interface: interfaceType,
tdsVersion: tdsVersion,
progName: progName,
progVersion: {
major: major,
minor: minor,
buildNumHi: buildNumHi,
buildNumLow: buildNumLow
}
}));
});
});
});
});
});
let data!: LoginAckToken;
try {
data = parseToken(parser);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
loginAckParser(parser, _options, callback);
});
});
});
}
}

callback(data);
}

export default loginAckParser;
Expand Down
42 changes: 24 additions & 18 deletions src/token/order-token-parser.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
// s2.2.7.14
import BufferReader from './buffer-reader';
import Parser, { ParserOptions } from './stream-parser';

import { OrderToken } from './token';

function orderParser(parser: Parser, _options: ParserOptions, callback: (token: OrderToken) => void) {
parser.readUInt16LE((length) => {
const columnCount = length / 2;
const orderColumns: number[] = [];
class NotEnoughDataError extends Error { }

function parseToken(parser: Parser): OrderToken {
const br = new BufferReader(parser);

let i = 0;
function next(done: () => void) {
if (i === columnCount) {
return done();
}
const length = br.readUInt16LE();
const columnCount = length / 2;
const orderColumns: number[] = [];

parser.readUInt16LE((column) => {
orderColumns.push(column);
for (let i = 0; i < columnCount; i++) {
const column = br.readUInt16LE();
orderColumns.push(column);
}

i++;
return new OrderToken(orderColumns);
}

next(done);
function orderParser(parser: Parser, _options: ParserOptions, callback: (token: OrderToken) => void) {
let data!: OrderToken;
try {
data = parseToken(parser);
} catch (err) {
if (err instanceof NotEnoughDataError) {
return parser.suspend(() => {
orderParser(parser, _options, callback);
});
}

next(() => {
callback(new OrderToken(orderColumns));
});
});
}
callback(data);
}

export default orderParser;
Expand Down
Loading